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