Coverage Report

Created: 2025-11-24 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libxmlb/src/xb-value-bindings.c
Line
Count
Source
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2
 * vi:set noexpandtab tabstop=8 shiftwidth=8:
3
 *
4
 * Copyright 2020 Endless OS Foundation LLC
5
 *
6
 * Author: Philip Withnall <withnall@endlessm.com>
7
 *
8
 * SPDX-License-Identifier: LGPL-2.1-or-later
9
 */
10
11
0
#define G_LOG_DOMAIN "XbValueBindings"
12
13
#include "config.h"
14
15
#include <glib.h>
16
17
#include "xb-opcode-private.h"
18
#include "xb-silo-private.h"
19
#include "xb-value-bindings-private.h"
20
21
typedef struct {
22
  guint8 kind; /* XbBoundValueKind */
23
  guint32 val;
24
  gpointer ptr;
25
  GDestroyNotify destroy_func;
26
} XbBoundValue;
27
28
typedef enum {
29
  XB_BOUND_VALUE_KIND_NONE,
30
  XB_BOUND_VALUE_KIND_TEXT,
31
  XB_BOUND_VALUE_KIND_INTEGER,
32
  XB_BOUND_VALUE_KIND_INDEXED_TEXT,
33
} XbBoundValueKind;
34
35
typedef struct {
36
  /* Currently limited to 4 values since that’s all that any client
37
   * uses. This could be expanded to dynamically allow more in future. */
38
  XbBoundValue values[4];
39
  gpointer dummy[3];
40
} RealValueBindings;
41
42
0
G_DEFINE_BOXED_TYPE(XbValueBindings,
43
0
        xb_value_bindings,
44
0
        xb_value_bindings_copy,
45
0
        xb_value_bindings_free)
46
0
47
0
/**
48
0
 * xb_value_bindings_init:
49
0
 * @self: an uninitialised #XbValueBindings to initialise
50
0
 *
51
0
 * Initialise a stack-allocated #XbValueBindings struct so it can be used.
52
0
 *
53
0
 * Stack-allocated #XbValueBindings instances should be freed once finished
54
0
 * with, using xb_value_bindings_clear() (or `g_auto(XbValueBindings)`, which is
55
0
 * equivalent).
56
0
 *
57
0
 * Since: 0.3.0
58
0
 */
59
0
void
60
0
xb_value_bindings_init(XbValueBindings *self)
61
0
{
62
0
  RealValueBindings *_self = (RealValueBindings *)self;
63
64
0
  for (gsize i = 0; i < G_N_ELEMENTS(_self->values); i++)
65
0
    _self->values[i].kind = XB_BOUND_VALUE_KIND_NONE;
66
0
}
67
68
static void
69
xb_value_bindings_clear_index(XbValueBindings *self, guint idx)
70
0
{
71
0
  RealValueBindings *_self = (RealValueBindings *)self;
72
73
0
  g_return_if_fail(idx < G_N_ELEMENTS(_self->values));
74
75
0
  if (_self->values[idx].ptr != NULL && _self->values[idx].destroy_func)
76
0
    _self->values[idx].destroy_func(_self->values[idx].ptr);
77
0
  _self->values[idx].kind = XB_BOUND_VALUE_KIND_NONE;
78
0
  _self->values[idx].ptr = NULL;
79
0
  _self->values[idx].destroy_func = NULL;
80
0
}
81
82
/**
83
 * xb_value_bindings_clear:
84
 * @self: an #XbValueBindings
85
 *
86
 * Clear an #XbValueBindings, freeing any allocated memory it points to.
87
 *
88
 * After this function has been called, the contents of the #XbValueBindings are
89
 * undefined, and it’s only safe to call xb_value_bindings_init() on it.
90
 *
91
 * Since: 0.3.0
92
 */
93
void
94
xb_value_bindings_clear(XbValueBindings *self)
95
0
{
96
0
  RealValueBindings *_self = (RealValueBindings *)self;
97
98
0
  for (gsize i = 0; i < G_N_ELEMENTS(_self->values); i++)
99
0
    xb_value_bindings_clear_index(self, i);
100
0
}
101
102
/* private */
103
gchar *
104
xb_value_bindings_to_string(XbValueBindings *self)
105
0
{
106
0
  RealValueBindings *_self = (RealValueBindings *)self;
107
0
  g_autoptr(GString) str = g_string_new("");
108
109
0
  for (guint i = 0; i < G_N_ELEMENTS(_self->values); i++) {
110
0
    XbBoundValue *value = &_self->values[i];
111
0
    if (value->kind == XB_BOUND_VALUE_KIND_NONE)
112
0
      continue;
113
0
    if (str->len > 0)
114
0
      g_string_append(str, ", ");
115
0
    if (value->kind == XB_BOUND_VALUE_KIND_INTEGER)
116
0
      g_string_append_printf(str, "?%u → %u", i, value->val);
117
0
    else if (value->kind == XB_BOUND_VALUE_KIND_TEXT && value->val > 0)
118
0
      g_string_append_printf(str,
119
0
                 "?%u → %s [%u]",
120
0
                 i,
121
0
                 (const gchar *)value->ptr,
122
0
                 value->val);
123
0
    else if (value->kind == XB_BOUND_VALUE_KIND_TEXT)
124
0
      g_string_append_printf(str, "?%u → %s", i, (const gchar *)value->ptr);
125
0
  }
126
0
  return g_string_free(g_steal_pointer(&str), FALSE);
127
0
}
128
129
/**
130
 * xb_value_bindings_copy:
131
 * @self: an #XbValueBindings
132
 *
133
 * Copy @self into a new heap-allocated #XbValueBindings instance.
134
 *
135
 * Returns: (transfer full): a copy of @self
136
 * Since: 0.3.0
137
 */
138
XbValueBindings *
139
xb_value_bindings_copy(XbValueBindings *self)
140
0
{
141
0
  RealValueBindings *_self = (RealValueBindings *)self;
142
0
  g_autoptr(XbValueBindings) copy = g_new0(XbValueBindings, 1);
143
144
0
  xb_value_bindings_init(copy);
145
146
0
  for (gsize i = 0; i < G_N_ELEMENTS(_self->values); i++) {
147
0
    gboolean copied = xb_value_bindings_copy_binding(self, i, copy, i);
148
0
    g_assert(copied);
149
0
  }
150
151
0
  return g_steal_pointer(&copy);
152
0
}
153
154
/**
155
 * xb_value_bindings_free:
156
 * @self: a heap-allocated #XbValueBindings
157
 *
158
 * Free a heap-allocated #XbValueBindings instance. This should be used on
159
 * #XbValueBindings instances created with xb_value_bindings_copy().
160
 *
161
 * For stack-allocated instances, xb_value_bindings_clear() should be used
162
 * instead.
163
 *
164
 * Since: 0.3.0
165
 */
166
void
167
xb_value_bindings_free(XbValueBindings *self)
168
0
{
169
0
  g_return_if_fail(self != NULL);
170
171
0
  xb_value_bindings_clear(self);
172
0
  g_free(self);
173
0
}
174
175
/**
176
 * xb_value_bindings_is_bound:
177
 * @self: an #XbValueBindings
178
 * @idx: 0-based index of the binding to check
179
 *
180
 * Check whether a value has been bound to the given index using (for example)
181
 * xb_value_bindings_bind_str().
182
 *
183
 * Returns: %TRUE if a value is bound to @idx, %FALSE otherwise
184
 * Since: 0.3.0
185
 */
186
gboolean
187
xb_value_bindings_is_bound(XbValueBindings *self, guint idx)
188
0
{
189
0
  RealValueBindings *_self = (RealValueBindings *)self;
190
191
0
  return (idx < G_N_ELEMENTS(_self->values) &&
192
0
    _self->values[idx].kind != XB_BOUND_VALUE_KIND_NONE);
193
0
}
194
195
/**
196
 * xb_value_bindings_bind_str:
197
 * @self: an #XbValueBindings
198
 * @idx: 0-based index to bind to
199
 * @str: (transfer full) (not nullable): a string to bind to @idx
200
 * @destroy_func: (nullable): function to free @str
201
 *
202
 * Bind @str to @idx in the value bindings.
203
 *
204
 * This will overwrite any previous binding at @idx. It will take ownership of
205
 * @str, and an appropriate @destroy_func must be provided to free @str once the
206
 * binding is no longer needed. @destroy_func will be called exactly once at
207
 * some point before the #XbValueBindings is cleared or freed.
208
 *
209
 * Since: 0.3.0
210
 */
211
void
212
xb_value_bindings_bind_str(XbValueBindings *self,
213
         guint idx,
214
         const gchar *str,
215
         GDestroyNotify destroy_func)
216
0
{
217
0
  RealValueBindings *_self = (RealValueBindings *)self;
218
219
0
  g_return_if_fail(self != NULL);
220
0
  g_return_if_fail(str != NULL);
221
222
  /* Currently limited to two values, but this restriction could be lifted
223
   * in future. */
224
0
  g_return_if_fail(idx < G_N_ELEMENTS(_self->values));
225
226
0
  xb_value_bindings_clear_index(self, idx);
227
228
0
  _self->values[idx].kind = XB_BOUND_VALUE_KIND_TEXT;
229
0
  _self->values[idx].ptr = (gpointer)str;
230
0
  _self->values[idx].destroy_func = destroy_func;
231
0
}
232
233
/**
234
 * xb_value_bindings_bind_val:
235
 * @self: an #XbValueBindings
236
 * @idx: 0-based index to bind to
237
 * @val: an integer to bind to @idx
238
 *
239
 * Bind @val to @idx in the value bindings.
240
 *
241
 * This will overwrite any previous binding at @idx.
242
 *
243
 * Since: 0.3.0
244
 */
245
void
246
xb_value_bindings_bind_val(XbValueBindings *self, guint idx, guint32 val)
247
0
{
248
0
  RealValueBindings *_self = (RealValueBindings *)self;
249
250
0
  g_return_if_fail(self != NULL);
251
252
  /* Currently limited to two values, but this restriction could be lifted
253
   * in future. */
254
0
  g_return_if_fail(idx < G_N_ELEMENTS(_self->values));
255
256
0
  xb_value_bindings_clear_index(self, idx);
257
258
0
  _self->values[idx].kind = XB_BOUND_VALUE_KIND_INTEGER;
259
0
  _self->values[idx].val = val;
260
0
  _self->values[idx].destroy_func = NULL;
261
0
}
262
263
/**
264
 * xb_value_bindings_lookup_opcode:
265
 * @self: an #XbValueBindings
266
 * @idx: 0-based index to look up the binding from
267
 * @opcode_out: (out caller-allocates) (not nullable): pointer to an #XbOpcode
268
 *     to initialise from the binding
269
 *
270
 * Initialises an #XbOpcode with the value bound to @idx, if a value is bound.
271
 * If no value is bound, @opcode_out is not touched and %FALSE is returned.
272
 *
273
 * @opcode_out is initialised to point to the data inside the #XbValueBindings,
274
 * so must have a shorter lifetime than the #XbValueBindings. It will be of kind
275
 * %XB_OPCODE_KIND_BOUND_TEXT or %XB_OPCODE_KIND_BOUND_INTEGER.
276
 *
277
 * Returns: %TRUE if @idx was bound, %FALSE otherwise
278
 * Since: 0.3.0
279
 */
280
gboolean
281
xb_value_bindings_lookup_opcode(XbValueBindings *self, guint idx, XbOpcode *opcode_out)
282
0
{
283
0
  RealValueBindings *_self = (RealValueBindings *)self;
284
285
0
  if (!xb_value_bindings_is_bound(self, idx))
286
0
    return FALSE;
287
288
0
  switch (_self->values[idx].kind) {
289
0
  case XB_BOUND_VALUE_KIND_TEXT:
290
0
    xb_opcode_init(opcode_out,
291
0
             XB_OPCODE_KIND_BOUND_TEXT,
292
0
             _self->values[idx].ptr,
293
0
             0,
294
0
             NULL);
295
0
    break;
296
0
  case XB_BOUND_VALUE_KIND_INTEGER:
297
0
    xb_opcode_init(opcode_out,
298
0
             XB_OPCODE_KIND_BOUND_INTEGER,
299
0
             NULL,
300
0
             _self->values[idx].val,
301
0
             NULL);
302
0
    break;
303
0
  case XB_BOUND_VALUE_KIND_INDEXED_TEXT:
304
0
    xb_opcode_init(opcode_out,
305
0
             XB_OPCODE_KIND_BOUND_INDEXED_TEXT,
306
0
             _self->values[idx].ptr,
307
0
             _self->values[idx].val,
308
0
             NULL);
309
0
    break;
310
0
  case XB_BOUND_VALUE_KIND_NONE:
311
0
  default:
312
0
    g_assert_not_reached();
313
0
  }
314
315
0
  return TRUE;
316
0
}
317
318
/**
319
 * xb_value_bindings_copy_binding:
320
 * @self: an #XbValueBindings to copy from
321
 * @idx: 0-based index to look up the binding from in @self
322
 * @dest: an #XbValueBindings to copy to
323
 * @dest_idx: 0-based index to copy the binding to in @dest
324
 *
325
 * Copies the value bound at @idx on @self to @dest_idx on @dest. If no value is
326
 * bound at @idx, @dest is not modified and %FALSE is returned.
327
 *
328
 * @dest must be initialised. If a binding already exists at @dest_idx, it will
329
 * be overwritten.
330
 *
331
 * Returns: %TRUE if @idx was bound, %FALSE otherwise
332
 * Since: 0.3.0
333
 */
334
gboolean
335
xb_value_bindings_copy_binding(XbValueBindings *self,
336
             guint idx,
337
             XbValueBindings *dest,
338
             guint dest_idx)
339
0
{
340
0
  RealValueBindings *_self = (RealValueBindings *)self;
341
0
  RealValueBindings *_dest = (RealValueBindings *)dest;
342
343
0
  if (!xb_value_bindings_is_bound(self, idx))
344
0
    return FALSE;
345
346
0
  switch (_self->values[idx].kind) {
347
0
  case XB_BOUND_VALUE_KIND_TEXT:
348
0
    xb_value_bindings_bind_str(dest, dest_idx, _self->values[idx].ptr, NULL);
349
0
    break;
350
0
  case XB_BOUND_VALUE_KIND_INTEGER:
351
0
    xb_value_bindings_bind_val(dest, dest_idx, _self->values[idx].val);
352
0
    break;
353
0
  case XB_BOUND_VALUE_KIND_INDEXED_TEXT:
354
0
    xb_value_bindings_bind_str(dest, dest_idx, _self->values[idx].ptr, NULL);
355
0
    _dest->values[idx].kind = XB_BOUND_VALUE_KIND_INDEXED_TEXT;
356
0
    _dest->values[idx].val = _self->values[idx].val;
357
0
    break;
358
0
  case XB_BOUND_VALUE_KIND_NONE:
359
0
  default:
360
0
    g_assert_not_reached();
361
0
  }
362
363
0
  return TRUE;
364
0
}
365
366
/* private */
367
gboolean
368
xb_value_bindings_indexed_text_lookup(XbValueBindings *self, XbSilo *silo, GError **error)
369
0
{
370
0
  RealValueBindings *_self = (RealValueBindings *)self;
371
0
  for (guint i = 0; i < G_N_ELEMENTS(_self->values); i++) {
372
0
    XbBoundValue *value = &_self->values[i];
373
0
    if (value->kind == XB_BOUND_VALUE_KIND_TEXT) {
374
0
      guint32 val = xb_silo_strtab_index_lookup(silo, (const gchar *)value->ptr);
375
0
      if (val == XB_SILO_UNSET) {
376
0
        g_set_error(error,
377
0
              G_IO_ERROR,
378
0
              G_IO_ERROR_INVALID_ARGUMENT,
379
0
              "indexed string '%s' was unfound",
380
0
              (const gchar *)value->ptr);
381
0
        return FALSE;
382
0
      }
383
0
      value->kind = XB_BOUND_VALUE_KIND_INDEXED_TEXT;
384
0
      value->val = val;
385
0
    }
386
0
  }
387
0
  return TRUE;
388
0
}