1 /*
2 * breakpoints.c
3 *
4 * Copyright 2010 Alexander Petukhov <devel(at)apetukhov.ru>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22 /*
23 * Functions for manipulatins breakpoints and quering breakpoints state.
24 * Modifying functions do all job regarding markers and
25 * entries in breaks tree view in the debugger panel
26 */
27
28 #include <string.h>
29
30 #include <geanyplugin.h>
31
32 #include "breakpoints.h"
33 #include "utils.h"
34 #include "markers.h"
35 #include "debug.h"
36 #include "bptree.h"
37 #include "dconfig.h"
38
39 /* container for break-for-file g_tree GTree-s */
40 GHashTable* files = NULL;
41
42 /*
43 * Functions for breakpoint iteration support
44 */
45
46 typedef void (*breaks_iterate_function)(void* bp);
47
48 /*
49 * Iterates through GTree for the particular file
50 */
tree_foreach_call_function(gpointer key,gpointer value,gpointer data)51 static gboolean tree_foreach_call_function(gpointer key, gpointer value, gpointer data)
52 {
53 ((breaks_iterate_function)data)(value);
54 return FALSE;
55 }
56
57 /*
58 * Iterates through hash table of GTree-s
59 */
hash_table_foreach_call_function(gpointer key,gpointer value,gpointer user_data)60 static void hash_table_foreach_call_function(gpointer key, gpointer value, gpointer user_data)
61 {
62 g_tree_foreach((GTree*)value, tree_foreach_call_function, user_data);
63 }
64
65 /*
66 * Iterates through GTree
67 * adding each item to GList that is passed through data variable
68 */
tree_foreach_add_to_list(gpointer key,gpointer value,gpointer data)69 static gboolean tree_foreach_add_to_list(gpointer key, gpointer value, gpointer data)
70 {
71 GList **list = (GList**)data;
72 *list = g_list_prepend(*list, value);
73 return FALSE;
74 }
75
76 /*
77 * Iterates through hash table of GTree-s
78 * calling list collection functions on each tree
79 */
hash_table_foreach_add_to_list(gpointer key,gpointer value,gpointer user_data)80 static void hash_table_foreach_add_to_list(gpointer key, gpointer value, gpointer user_data)
81 {
82 g_tree_foreach((GTree*)value, tree_foreach_add_to_list, user_data);
83 }
84
85 /*
86 * functions to perform markers and tree vew operation when breakpoint
87 * is finally updated/added/removed
88 */
on_add(breakpoint * bp)89 static void on_add(breakpoint *bp)
90 {
91 /* add to breakpoints tab */
92 bptree_add_breakpoint(bp);
93 /* add marker */
94 markers_add_breakpoint(bp);
95 }
on_remove(breakpoint * bp)96 static void on_remove(breakpoint *bp)
97 {
98 GTree *tree;
99
100 /* remove marker */
101 markers_remove_breakpoint(bp);
102 /* remove from breakpoints tab */
103 bptree_remove_breakpoint(bp);
104 /* remove from internal storage */
105 tree = g_hash_table_lookup(files, bp->file);
106 g_tree_remove(tree, GINT_TO_POINTER(bp->line));
107 }
on_set_hits_count(breakpoint * bp)108 static void on_set_hits_count(breakpoint *bp)
109 {
110 bptree_set_hitscount(bp);
111 markers_remove_breakpoint(bp);
112 markers_add_breakpoint(bp);
113 }
on_set_condition(breakpoint * bp)114 static void on_set_condition(breakpoint* bp)
115 {
116 /* set condition in breaks tree */
117 bptree_set_condition(bp);
118 markers_remove_breakpoint(bp);
119 markers_add_breakpoint(bp);
120 }
on_switch(breakpoint * bp)121 static void on_switch(breakpoint *bp)
122 {
123 /* remove old and set new marker */
124 markers_remove_breakpoint(bp);
125 markers_add_breakpoint(bp);
126
127 /* set checkbox in breaks tree */
128 bptree_set_enabled(bp);
129 }
on_set_enabled_list(GList * breaks,gboolean enabled)130 static void on_set_enabled_list(GList *breaks, gboolean enabled)
131 {
132 GList *iter = breaks;
133 while (iter)
134 {
135 breakpoint *bp = (breakpoint*)iter->data;
136
137 if (bp->enabled ^ enabled)
138 {
139 bp->enabled = enabled;
140
141 /* remove old and set new marker */
142 markers_remove_breakpoint(bp);
143 markers_add_breakpoint(bp);
144
145 /* set checkbox in breaks tree */
146 bptree_set_enabled(bp);
147 }
148 iter = iter->next;
149 }
150 }
on_remove_list(GList * list)151 static void on_remove_list(GList *list)
152 {
153 GList *iter;
154 for (iter = list; iter; iter = iter->next)
155 {
156 on_remove((breakpoint*)iter->data);
157 }
158 }
159
160 /*
161 * Helper functions
162 */
163
164 /*
165 * compare pointers as integers
166 * return value similar to strcmp
167 * arguments:
168 * a - first integer
169 * b - second integer
170 * user_data - not used
171 */
compare_func(gconstpointer a,gconstpointer b,gpointer user_data)172 static gint compare_func(gconstpointer a, gconstpointer b, gpointer user_data)
173 {
174 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
175 }
176
177 /*
178 * functions that are called when a breakpoint is altered while debuginng session is active.
179 * Therefore, these functions try to alter break in debug session first and if successful -
180 * do what on_... do or simply call on_... function directly
181 */
breaks_add_debug(breakpoint * bp)182 static void breaks_add_debug(breakpoint* bp)
183 {
184 if (debug_set_break(bp, BSA_NEW_BREAK))
185 {
186 /* add markers, update treeview */
187 on_add(bp);
188 /* mark config for saving */
189 config_set_debug_changed();
190 }
191 else
192 dialogs_show_msgbox(GTK_MESSAGE_ERROR, "%s", debug_error_message());
193 }
breaks_remove_debug(breakpoint * bp)194 static void breaks_remove_debug(breakpoint* bp)
195 {
196 if (debug_remove_break(bp))
197 {
198 /* remove markers, update treeview */
199 on_remove(bp);
200 /* mark config for saving */
201 config_set_debug_changed();
202 }
203 else
204 dialogs_show_msgbox(GTK_MESSAGE_ERROR, "%s", debug_error_message());
205 }
breaks_set_hits_count_debug(breakpoint * bp)206 static void breaks_set_hits_count_debug(breakpoint* bp)
207 {
208 if (debug_set_break(bp, BSA_UPDATE_HITS_COUNT))
209 {
210 on_set_hits_count(bp);
211 /* mark config for saving */
212 config_set_debug_changed();
213 }
214 else
215 dialogs_show_msgbox(GTK_MESSAGE_ERROR, "%s", debug_error_message());
216 }
breaks_set_condition_debug(breakpoint * bp)217 static void breaks_set_condition_debug(breakpoint* bp)
218 {
219 if (debug_set_break(bp, BSA_UPDATE_CONDITION))
220 {
221 on_set_condition(bp);
222 /* mark config for saving */
223 config_set_debug_changed();
224 }
225 else
226 {
227 /* revert to old condition (taken from tree) */
228 gchar* oldcondition = bptree_get_condition(bp);
229 strncpy(bp->condition, oldcondition, G_N_ELEMENTS(bp->condition) - 1);
230 g_free(oldcondition);
231 /* show error message */
232 dialogs_show_msgbox(GTK_MESSAGE_ERROR, "%s", debug_error_message());
233 }
234 }
breaks_switch_debug(breakpoint * bp)235 static void breaks_switch_debug(breakpoint* bp)
236 {
237 if (debug_set_break(bp, BSA_UPDATE_ENABLE))
238 {
239 on_switch(bp);
240 /* mark config for saving */
241 config_set_debug_changed();
242 }
243 else
244 {
245 bp->enabled = !bp->enabled;
246 dialogs_show_msgbox(GTK_MESSAGE_ERROR, "%s", debug_error_message());
247 }
248 }
breaks_set_disabled_list_debug(GList * list)249 static void breaks_set_disabled_list_debug(GList *list)
250 {
251 GList *iter;
252 for (iter = list; iter; iter = iter->next)
253 {
254 breakpoint *bp = (breakpoint*)iter->data;
255 if (bp->enabled)
256 {
257 bp->enabled = FALSE;
258 if (debug_set_break(bp, BSA_UPDATE_ENABLE))
259 {
260 on_switch(bp);
261 }
262 else
263 {
264 bp->enabled = TRUE;
265 }
266 }
267 }
268 g_list_free(list);
269
270 config_set_debug_changed();
271 }
breaks_set_enabled_list_debug(GList * list)272 static void breaks_set_enabled_list_debug(GList *list)
273 {
274 GList *iter;
275 for (iter = list; iter; iter = iter->next)
276 {
277 breakpoint *bp = (breakpoint*)iter->data;
278 if (!bp->enabled)
279 {
280 bp->enabled = TRUE;
281 if (debug_set_break(bp, BSA_UPDATE_ENABLE))
282 {
283 on_switch(bp);
284 }
285 else
286 {
287 bp->enabled = FALSE;
288 }
289 }
290 }
291 g_list_free(list);
292
293 config_set_debug_changed();
294 }
breaks_remove_list_debug(GList * list)295 static void breaks_remove_list_debug(GList *list)
296 {
297 GList *iter;
298 for (iter = list; iter; iter = iter->next)
299 {
300 breakpoint *bp = (breakpoint*)iter->data;
301 if (debug_remove_break(bp))
302 {
303 on_remove((breakpoint*)iter->data);
304 }
305 }
306 g_list_free(list);
307
308 config_set_debug_changed();
309 }
310
311 /*
312 * Init breaks related data.
313 * arguments:
314 * cb - callback to call on breakpoints tree view double click
315 */
breaks_init(move_to_line_cb cb)316 gboolean breaks_init(move_to_line_cb cb)
317 {
318 /* create breakpoints storage */
319 files = g_hash_table_new_full(
320 g_str_hash,
321 g_str_equal,
322 (GDestroyNotify)g_free,
323 (GDestroyNotify)g_tree_destroy);
324
325 /* create breaks tab page control */
326 bptree_init(cb);
327
328 return TRUE;
329 }
330
331 /*
332 * Frees breaks related data.
333 */
breaks_destroy(void)334 void breaks_destroy(void)
335 {
336 /* remove all markers */
337 GList *breaks, *iter;
338 breaks = iter = breaks_get_all();
339 while (iter)
340 {
341 markers_remove_breakpoint((breakpoint*)iter->data);
342 iter = iter->next;
343 }
344 g_list_free(breaks);
345
346 /* free storage */
347 g_hash_table_destroy(files);
348
349 /* destroy breaks tree data */
350 bptree_destroy();
351 }
352
353 /*
354 * Add new breakpoint.
355 * arguments:
356 * file - breakpoints filename
357 * line - breakpoints line
358 * condition - breakpoints line
359 * enabled - is new breakpoint enabled
360 * hitscount - breakpoints hitscount
361 */
breaks_add(const char * file,int line,char * condition,int enabled,int hitscount)362 void breaks_add(const char* file, int line, char* condition, int enabled, int hitscount)
363 {
364 GTree *tree;
365 breakpoint* bp;
366 enum dbs state = debug_get_state();
367
368 /* do not process async break manipulation on modules
369 that do not support async interuppt */
370 if (DBS_RUNNING == state && !debug_supports_async_breaks())
371 return;
372
373 /* allocate memory */
374 bp = break_new_full(file, line, condition, enabled, hitscount);
375
376 /* check whether GTree for this file exists and create if doesn't */
377 if (!(tree = g_hash_table_lookup(files, bp->file)))
378 {
379 char *newfile = g_strdup(bp->file);
380 tree = g_tree_new_full(compare_func, NULL, NULL, (GDestroyNotify)g_free);
381 g_hash_table_insert(files, newfile, tree);
382 }
383
384 /* insert to internal storage */
385 g_tree_insert(tree, GINT_TO_POINTER(bp->line), bp);
386
387 /* handle creation instantly if debugger is idle or stopped
388 and request debug module interruption overwise */
389 if (DBS_IDLE == state)
390 {
391 on_add(bp);
392 config_set_debug_changed();
393 }
394 else if (DBS_STOPPED == state)
395 breaks_add_debug(bp);
396 else if (DBS_STOP_REQUESTED != state)
397 debug_request_interrupt((bs_callback)breaks_add_debug, (gpointer)bp);
398 }
399
400 /*
401 * Remove breakpoint.
402 * arguments:
403 * file - breakpoints filename
404 * line - breakpoints line
405 */
breaks_remove(const char * file,int line)406 void breaks_remove(const char* file, int line)
407 {
408 breakpoint* bp = NULL;
409 enum dbs state = debug_get_state();
410
411 /* do not process async break manipulation on modules
412 that do not support async interuppt */
413 if (DBS_RUNNING == state && !debug_supports_async_breaks())
414 return;
415
416 /* lookup for breakpoint */
417 if (!(bp = breaks_lookup_breakpoint(file, line)))
418 return;
419
420 /* handle removing instantly if debugger is idle or stopped
421 and request debug module interruption overwise */
422 if (DBS_IDLE == state)
423 {
424 on_remove(bp);
425 config_set_debug_changed();
426 }
427 else if (DBS_STOPPED == state)
428 breaks_remove_debug(bp);
429 else if (DBS_STOP_REQUESTED != state)
430 debug_request_interrupt((bs_callback)breaks_remove_debug, (gpointer)bp);
431 }
432
433 /*
434 * Remove all breakpoints in the list.
435 * arguments:
436 * list - list f breakpoints
437 */
breaks_remove_list(GList * list)438 void breaks_remove_list(GList *list)
439 {
440 /* do not process async break manipulation on modules
441 that do not support async interuppt */
442 enum dbs state = debug_get_state();
443 if (DBS_RUNNING == state && !debug_supports_async_breaks())
444 return;
445
446 /* handle removing instantly if debugger is idle or stopped
447 and request debug module interruption overwise */
448 if (DBS_IDLE == state)
449 {
450 on_remove_list(list);
451 g_list_free(list);
452
453 config_set_debug_changed();
454 }
455 else if (DBS_STOPPED == state)
456 breaks_remove_list_debug(list);
457 else if (DBS_STOP_REQUESTED != state)
458 debug_request_interrupt((bs_callback)breaks_remove_list_debug, (gpointer)list);
459 }
460
461 /*
462 * Removes all breakpoints.
463 * arguments:
464 */
breaks_remove_all(void)465 void breaks_remove_all(void)
466 {
467 g_hash_table_foreach(files, hash_table_foreach_call_function, (gpointer)on_remove);
468 g_hash_table_remove_all(files);
469 }
470
471 /*
472 * sets all breakpoints fo the file enabled or disabled.
473 * arguments:
474 * file - list of breakpoints
475 * enabled - anble or disable breakpoints
476 */
breaks_set_enabled_for_file(const char * file,gboolean enabled)477 void breaks_set_enabled_for_file(const char *file, gboolean enabled)
478 {
479 GList *breaks;
480 enum dbs state = debug_get_state();
481
482 /* do not process async break manipulation on modules
483 that do not support async interuppt */
484 if (DBS_RUNNING == state && !debug_supports_async_breaks())
485 return;
486
487 breaks = breaks_get_for_document(file);
488
489 /* handle switching instantly if debugger is idle or stopped
490 and request debug module interruption overwise */
491 if (DBS_IDLE == state)
492 {
493 on_set_enabled_list(breaks, enabled);
494 g_list_free(breaks);
495 config_set_debug_changed();
496 }
497 else if (DBS_STOPPED == state)
498 enabled ? breaks_set_enabled_list_debug(breaks) : breaks_set_disabled_list_debug(breaks);
499 else if (DBS_STOP_REQUESTED != state)
500 debug_request_interrupt((bs_callback)(enabled ? breaks_set_enabled_list_debug : breaks_set_disabled_list_debug), (gpointer)breaks);
501 }
502
503 /*
504 * Switch breakpoints state.
505 * arguments:
506 * file - breakpoints filename
507 * line - breakpoints line
508 */
breaks_switch(const char * file,int line)509 void breaks_switch(const char* file, int line)
510 {
511 breakpoint* bp = NULL;
512 enum dbs state = debug_get_state();
513
514 /* do not process async break manipulation on modules
515 that do not support async interuppt */
516 if (DBS_RUNNING == state && !debug_supports_async_breaks())
517 return;
518
519 /* lookup for breakpoint */
520 if (!(bp = breaks_lookup_breakpoint(file, line)))
521 return;
522
523 /* change activeness */
524 bp->enabled = !bp->enabled;
525
526 /* handle switching instantly if debugger is idle or stopped
527 and request debug module interruption overwise */
528 if (DBS_IDLE == state)
529 {
530 on_switch(bp);
531 config_set_debug_changed();
532 }
533 else if (DBS_STOPPED == state)
534 breaks_switch_debug(bp);
535 else if (DBS_STOP_REQUESTED != state)
536 debug_request_interrupt((bs_callback)breaks_switch_debug, (gpointer)bp);
537 }
538
539 /*
540 * Set breakpoints hits count.
541 * arguments:
542 * file - breakpoints filename
543 * line - breakpoints line
544 * count - breakpoints hitscount
545 */
breaks_set_hits_count(const char * file,int line,int count)546 void breaks_set_hits_count(const char* file, int line, int count)
547 {
548 breakpoint* bp = NULL;
549 enum dbs state = debug_get_state();
550
551 /* do not process async break manipulation on modules
552 that do not support async interuppt */
553 if (DBS_RUNNING == state && !debug_supports_async_breaks())
554 return;
555
556 /* lookup for breakpoint */
557 if (!(bp = breaks_lookup_breakpoint(file, line)))
558 return;
559
560 /* change hits count */
561 bp->hitscount = count;
562
563 /* handle setting hits count instantly if debugger is idle or stopped
564 and request debug module interruption overwise */
565 if (state == DBS_IDLE)
566 {
567 on_set_hits_count(bp);
568 config_set_debug_changed();
569 }
570 else if(state == DBS_STOPPED)
571 breaks_set_hits_count_debug(bp);
572 else if (state != DBS_STOP_REQUESTED)
573 debug_request_interrupt((bs_callback)breaks_set_hits_count_debug, (gpointer)bp);
574 }
575
576 /*
577 * Set breakpoints condition.
578 * arguments:
579 * file - breakpoints filename
580 * line - breakpoints line
581 * condition - breakpoints line
582 */
breaks_set_condition(const char * file,int line,const char * condition)583 void breaks_set_condition(const char* file, int line, const char* condition)
584 {
585 breakpoint* bp = NULL;
586 enum dbs state = debug_get_state();
587
588 /* do not process async break manipulation on modules
589 that do not support async interuppt */
590 if (DBS_RUNNING == state && !debug_supports_async_breaks())
591 return;
592
593 /* lookup for breakpoint */
594 if (!(bp = breaks_lookup_breakpoint(file, line)))
595 return;
596
597 /* change condition */
598 strncpy(bp->condition, condition, G_N_ELEMENTS(bp->condition) - 1);
599
600 /* handle setting condition instantly if debugger is idle or stopped
601 and request debug module interruption overwise */
602 if (state == DBS_IDLE)
603 {
604 on_set_condition(bp);
605 config_set_debug_changed();
606 }
607 else if (state == DBS_STOPPED)
608 breaks_set_condition_debug(bp);
609 else if (state != DBS_STOP_REQUESTED)
610 debug_request_interrupt((bs_callback)breaks_set_condition_debug, (gpointer)bp);
611 }
612
613 /*
614 * Moves a breakpoint from to another line
615 * arguments:
616 * file - breakpoints filename
617 * line_from - old line number
618 * line_to - new line number
619 */
breaks_move_to_line(const char * file,int line_from,int line_to)620 void breaks_move_to_line(const char* file, int line_from, int line_to)
621 {
622 /* first look for the tree for the given file */
623 GTree *tree = NULL;
624 if ( (tree = g_hash_table_lookup(files, file)) )
625 {
626 /* lookup for the break in GTree*/
627 breakpoint *bp = (breakpoint*)g_tree_lookup(tree, GINT_TO_POINTER(line_from));
628 if (bp)
629 {
630 g_tree_steal(tree, GINT_TO_POINTER(line_from));
631 bp->line = line_to;
632 g_tree_insert(tree, GINT_TO_POINTER(line_to), bp);
633
634 /* mark config for saving */
635 config_set_debug_changed();
636 }
637 }
638 }
639
640 /*
641 * Checks whether breakpoint is set.
642 * arguments:
643 * file - breakpoints filename
644 * line - breakpoints line
645 */
breaks_get_state(const char * file,int line)646 break_state breaks_get_state(const char* file, int line)
647 {
648 break_state bs = BS_NOT_SET;
649 GTree *tree;
650
651 /* first look for the tree for the given file */
652 if ( (tree = g_hash_table_lookup(files, file)) )
653 {
654 breakpoint *bp = g_tree_lookup(tree, GINT_TO_POINTER(line));
655 if (bp)
656 {
657 bs = bp->enabled ? BS_ENABLED : BS_DISABLED;
658 }
659 }
660
661 return bs;
662 }
663
664 /*
665 * Get breakpoints GTree for the given file
666 * arguments:
667 * file - file name to get breaks for
668 */
breaks_get_for_document(const char * file)669 GList* breaks_get_for_document(const char* file)
670 {
671 GList *breaks = NULL;
672 GTree *tree = g_hash_table_lookup(files, file);
673 if (tree)
674 {
675 g_tree_foreach(tree, tree_foreach_add_to_list, &breaks);
676 }
677 return g_list_reverse(breaks);
678 }
679
680 /*
681 * lookup for breakpoint
682 * arguments:
683 * file - breakpoints filename
684 * line - breakpoints line
685 */
breaks_lookup_breakpoint(const gchar * file,int line)686 breakpoint* breaks_lookup_breakpoint(const gchar* file, int line)
687 {
688 breakpoint* bp = NULL;
689 GTree* tree = NULL;
690 if ( (tree = (GTree*)g_hash_table_lookup(files, file)) )
691 bp = g_tree_lookup(tree, GINT_TO_POINTER(line));
692
693 return bp;
694 }
695
696 /*
697 * Gets all breakpoints
698 * arguments:
699 */
breaks_get_all(void)700 GList* breaks_get_all(void)
701 {
702 GList *breaks = NULL;
703 g_hash_table_foreach(files, hash_table_foreach_add_to_list, &breaks);
704 return g_list_reverse(breaks);
705 }
706