1 /* ide-debugger-breakpoints.c
2 *
3 * Copyright 2017-2019 Christian Hergert <chergert@redhat.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21 #define G_LOG_DOMAIN "ide-debugger-breakpoints"
22
23 #include "config.h"
24
25 #include <stdlib.h>
26
27 #include "ide-debugger-breakpoints.h"
28 #include "ide-debugger-private.h"
29
30 /**
31 * SECTION:ide-debugger-breakpoints
32 * @title: IdeDebuggerBreakpoints
33 * @short_title: A collection of breakpoints for a file
34 *
35 * The #IdeDebuggerBreakpoints provides a convenient container for breakpoints
36 * about a single file. This is useful for situations like the document gutter
37 * where we need very fast access to whether or not a line has a breakpoint set
38 * during the rendering process.
39 *
40 * At it's core, this is a sparse array as rarely do we have more than one
41 * cacheline of information about breakpoints in a file.
42 *
43 * This object is controled by the IdeDebuggerManager and will modify the
44 * breakpoints as necessary by the current debugger. If no debugger is
45 * active, the breakpoints are queued until the debugger has started, and
46 * then synchronized to the debugger process.
47 *
48 * Since: 3.32
49 */
50
51 typedef struct
52 {
53 guint line;
54 IdeDebuggerBreakMode mode;
55 IdeDebuggerBreakpoint *breakpoint;
56 } LineInfo;
57
58 struct _IdeDebuggerBreakpoints
59 {
60 GObject parent_instance;
61 GArray *lines;
62 GFile *file;
63 };
64
65 enum {
66 PROP_0,
67 PROP_FILE,
68 N_PROPS
69 };
70
71 enum {
72 CHANGED,
73 N_SIGNALS
74 };
75
G_DEFINE_FINAL_TYPE(IdeDebuggerBreakpoints,ide_debugger_breakpoints,G_TYPE_OBJECT)76 G_DEFINE_FINAL_TYPE (IdeDebuggerBreakpoints, ide_debugger_breakpoints, G_TYPE_OBJECT)
77
78 static GParamSpec *properties [N_PROPS];
79 static guint signals [N_SIGNALS];
80
81 static void
82 line_info_clear (gpointer data)
83 {
84 LineInfo *info = data;
85
86 info->line = 0;
87 info->mode = 0;
88 g_clear_object (&info->breakpoint);
89 }
90
91 static gint
line_info_compare(gconstpointer a,gconstpointer b)92 line_info_compare (gconstpointer a,
93 gconstpointer b)
94 {
95 const LineInfo *lia = a;
96 const LineInfo *lib = b;
97
98 return (gint)lia->line - (gint)lib->line;
99 }
100
101 static void
ide_debugger_breakpoints_dispose(GObject * object)102 ide_debugger_breakpoints_dispose (GObject *object)
103 {
104 IdeDebuggerBreakpoints *self = (IdeDebuggerBreakpoints *)object;
105
106 g_clear_pointer (&self->lines, g_array_unref);
107
108 G_OBJECT_CLASS (ide_debugger_breakpoints_parent_class)->dispose (object);
109 }
110
111 static void
ide_debugger_breakpoints_finalize(GObject * object)112 ide_debugger_breakpoints_finalize (GObject *object)
113 {
114 IdeDebuggerBreakpoints *self = (IdeDebuggerBreakpoints *)object;
115
116 g_clear_object (&self->file);
117
118 G_OBJECT_CLASS (ide_debugger_breakpoints_parent_class)->finalize (object);
119 }
120
121 static void
ide_debugger_breakpoints_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)122 ide_debugger_breakpoints_get_property (GObject *object,
123 guint prop_id,
124 GValue *value,
125 GParamSpec *pspec)
126 {
127 IdeDebuggerBreakpoints *self = IDE_DEBUGGER_BREAKPOINTS (object);
128
129 switch (prop_id)
130 {
131 case PROP_FILE:
132 g_value_set_object (value, self->file);
133 break;
134
135 default:
136 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
137 }
138 }
139
140 static void
ide_debugger_breakpoints_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)141 ide_debugger_breakpoints_set_property (GObject *object,
142 guint prop_id,
143 const GValue *value,
144 GParamSpec *pspec)
145 {
146 IdeDebuggerBreakpoints *self = IDE_DEBUGGER_BREAKPOINTS (object);
147
148 switch (prop_id)
149 {
150 case PROP_FILE:
151 self->file = g_value_dup_object (value);
152 break;
153
154 default:
155 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
156 }
157 }
158
159 static void
ide_debugger_breakpoints_class_init(IdeDebuggerBreakpointsClass * klass)160 ide_debugger_breakpoints_class_init (IdeDebuggerBreakpointsClass *klass)
161 {
162 GObjectClass *object_class = G_OBJECT_CLASS (klass);
163
164 object_class->dispose = ide_debugger_breakpoints_dispose;
165 object_class->finalize = ide_debugger_breakpoints_finalize;
166 object_class->get_property = ide_debugger_breakpoints_get_property;
167 object_class->set_property = ide_debugger_breakpoints_set_property;
168
169 properties [PROP_FILE] =
170 g_param_spec_object ("file",
171 "File",
172 "The file for the breakpoints",
173 G_TYPE_FILE,
174 (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
175
176 g_object_class_install_properties (object_class, N_PROPS, properties);
177
178 signals [CHANGED] =
179 g_signal_new ("changed",
180 G_TYPE_FROM_CLASS (klass),
181 G_SIGNAL_RUN_LAST,
182 0, NULL, NULL,
183 g_cclosure_marshal_VOID__VOID,
184 G_TYPE_NONE, 0);
185 }
186
187 static void
ide_debugger_breakpoints_init(IdeDebuggerBreakpoints * self)188 ide_debugger_breakpoints_init (IdeDebuggerBreakpoints *self)
189 {
190 }
191
192 /**
193 * ide_debugger_breakpoints_get_line:
194 * @self: An #IdeDebuggerBreakpoints
195 * @line: The line number
196 *
197 * Gets the breakpoint that has been registered at a given line, or %NULL
198 * if no breakpoint is registered there.
199 *
200 * Returns: (nullable) (transfer none): An #IdeDebuggerBreakpoint or %NULL
201 *
202 * Since: 3.32
203 */
204 IdeDebuggerBreakpoint *
ide_debugger_breakpoints_get_line(IdeDebuggerBreakpoints * self,guint line)205 ide_debugger_breakpoints_get_line (IdeDebuggerBreakpoints *self,
206 guint line)
207 {
208 g_return_val_if_fail (IDE_IS_DEBUGGER_BREAKPOINTS (self), NULL);
209
210 if (self->lines != NULL)
211 {
212 LineInfo info = { line, 0 };
213 LineInfo *ret;
214
215 ret = bsearch (&info, (gpointer)self->lines->data,
216 self->lines->len, sizeof (LineInfo),
217 line_info_compare);
218
219 if (ret)
220 return ret->breakpoint;
221 }
222
223 return NULL;
224 }
225
226 IdeDebuggerBreakMode
ide_debugger_breakpoints_get_line_mode(IdeDebuggerBreakpoints * self,guint line)227 ide_debugger_breakpoints_get_line_mode (IdeDebuggerBreakpoints *self,
228 guint line)
229 {
230 g_return_val_if_fail (IDE_IS_DEBUGGER_BREAKPOINTS (self), 0);
231
232 if (self->lines != NULL)
233 {
234 LineInfo info = { line, 0 };
235 LineInfo *ret;
236
237 ret = bsearch (&info, (gpointer)self->lines->data,
238 self->lines->len, sizeof (LineInfo),
239 line_info_compare);
240
241 if (ret)
242 return ret->mode;
243 }
244
245 return 0;
246 }
247
248 static void
ide_debugger_breakpoints_set_line(IdeDebuggerBreakpoints * self,guint line,IdeDebuggerBreakMode mode,IdeDebuggerBreakpoint * breakpoint)249 ide_debugger_breakpoints_set_line (IdeDebuggerBreakpoints *self,
250 guint line,
251 IdeDebuggerBreakMode mode,
252 IdeDebuggerBreakpoint *breakpoint)
253 {
254 LineInfo info;
255
256 g_assert (IDE_IS_DEBUGGER_BREAKPOINTS (self));
257 g_assert (IDE_IS_DEBUGGER_BREAK_MODE (mode));
258 g_assert (!breakpoint || IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
259 g_assert (mode == IDE_DEBUGGER_BREAK_NONE || breakpoint != NULL);
260
261 if (self->lines != NULL)
262 {
263 for (guint i = 0; i < self->lines->len; i++)
264 {
265 LineInfo *ele = &g_array_index (self->lines, LineInfo, i);
266
267 if (ele->line == line)
268 {
269 if (mode != IDE_DEBUGGER_BREAK_NONE)
270 {
271 ele->mode = mode;
272 g_set_object (&ele->breakpoint, breakpoint);
273 }
274 else
275 g_array_remove_index (self->lines, i);
276
277 goto emit_signal;
278 }
279 }
280 }
281
282 /* Nothing to do here */
283 if (mode == IDE_DEBUGGER_BREAK_NONE)
284 return;
285
286 if (self->lines == NULL)
287 {
288 self->lines = g_array_new (FALSE, FALSE, sizeof (LineInfo));
289 g_array_set_clear_func (self->lines, line_info_clear);
290 }
291
292 info.line = line;
293 info.mode = mode;
294 info.breakpoint = g_object_ref (breakpoint);
295
296 g_array_append_val (self->lines, info);
297 g_array_sort (self->lines, line_info_compare);
298
299 emit_signal:
300 g_signal_emit (self, signals [CHANGED], 0);
301 }
302
303 void
_ide_debugger_breakpoints_add(IdeDebuggerBreakpoints * self,IdeDebuggerBreakpoint * breakpoint)304 _ide_debugger_breakpoints_add (IdeDebuggerBreakpoints *self,
305 IdeDebuggerBreakpoint *breakpoint)
306 {
307 IdeDebuggerBreakMode mode;
308 guint line;
309
310 IDE_ENTRY;
311
312 g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINTS (self));
313 g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
314
315 line = ide_debugger_breakpoint_get_line (breakpoint);
316 mode = ide_debugger_breakpoint_get_mode (breakpoint);
317
318 IDE_TRACE_MSG ("tracking breakpoint at line %d [breakpoints=%p]",
319 line, self);
320
321 ide_debugger_breakpoints_set_line (self, line, mode, breakpoint);
322
323 IDE_EXIT;
324 }
325
326 void
_ide_debugger_breakpoints_remove(IdeDebuggerBreakpoints * self,IdeDebuggerBreakpoint * breakpoint)327 _ide_debugger_breakpoints_remove (IdeDebuggerBreakpoints *self,
328 IdeDebuggerBreakpoint *breakpoint)
329 {
330 guint line;
331
332 IDE_ENTRY;
333
334 g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINTS (self));
335 g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
336
337 line = ide_debugger_breakpoint_get_line (breakpoint);
338
339 IDE_TRACE_MSG ("removing breakpoint at line %d [breakpoints=%p]",
340 line, self);
341
342 if (self->lines != NULL)
343 {
344 /* First try to get things by pointer address to reduce chances
345 * of removing the wrong breakpoint from the collection.
346 */
347 for (guint i = 0; i < self->lines->len; i++)
348 {
349 const LineInfo *info = &g_array_index (self->lines, LineInfo, i);
350
351 if (ide_debugger_breakpoint_compare (breakpoint, info->breakpoint) == 0)
352 {
353 g_array_remove_index (self->lines, i);
354 g_signal_emit (self, signals [CHANGED], 0);
355 IDE_EXIT;
356 }
357 }
358
359 ide_debugger_breakpoints_set_line (self, line, IDE_DEBUGGER_BREAK_NONE, NULL);
360 }
361
362 IDE_EXIT;
363 }
364
365 /**
366 * ide_debugger_breakpoints_get_file:
367 * @self: An #IdeDebuggerBreakpoints
368 *
369 * Gets the "file" property, which is the file that breakpoints within
370 * this container belong to.
371 *
372 * Returns: (transfer none): a #GFile
373 *
374 * Since: 3.32
375 */
376 GFile *
ide_debugger_breakpoints_get_file(IdeDebuggerBreakpoints * self)377 ide_debugger_breakpoints_get_file (IdeDebuggerBreakpoints *self)
378 {
379 g_return_val_if_fail (IDE_IS_DEBUGGER_BREAKPOINTS (self), NULL);
380
381 return self->file;
382 }
383
384 /**
385 * ide_debugger_breakpoints_foreach:
386 * @self: a #IdeDebuggerBreakpoints
387 * @func: (scope call) (closure user_data): a #GFunc to call
388 * @user_data: user data for @func
389 *
390 * Call @func for every #IdeDebuggerBreakpoint in @self.
391 *
392 * Since: 3.32
393 */
394 void
ide_debugger_breakpoints_foreach(IdeDebuggerBreakpoints * self,GFunc func,gpointer user_data)395 ide_debugger_breakpoints_foreach (IdeDebuggerBreakpoints *self,
396 GFunc func,
397 gpointer user_data)
398 {
399 g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINTS (self));
400 g_return_if_fail (func != NULL);
401
402 if (self->lines != NULL)
403 {
404 for (guint i = 0; i < self->lines->len; i++)
405 {
406 const LineInfo *info = &g_array_index (self->lines, LineInfo, i);
407
408 if (info->breakpoint)
409 func (info->breakpoint, user_data);
410 }
411 }
412 }
413