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