Coverage Report

Created: 2025-07-01 07:09

/src/glib/gio/strinfo.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright © 2010 Codethink Limited
3
 *
4
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (at your option) any later version.
8
 *
9
 * This library is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
 * Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
16
 *
17
 * Author: Ryan Lortie <desrt@desrt.ca>
18
 */
19
20
#include <string.h>
21
#include <glib.h>
22
23
/*
24
 * The string info map is an efficient data structure designed to be
25
 * used with a small set of items.  It is used by GSettings schemas for
26
 * three purposes:
27
 *
28
 *  1) Implement <choices> with a list of valid strings
29
 *
30
 *  2) Implement <alias> by mapping one string to another
31
 *
32
 *  3) Implement enumerated types by mapping strings to integer values
33
 *     (and back).
34
 *
35
 * The map is made out of an array of uint32s.  Each entry in the array
36
 * is an integer value, followed by a specially formatted string value:
37
 *
38
 *   The string starts with the byte 0xff or 0xfe, followed by the
39
 *   content of the string, followed by a nul byte, followed by
40
 *   additional nul bytes for padding, followed by a 0xff byte.
41
 *
42
 *   Padding is added so that the entire formatted string takes up a
43
 *   multiple of 4 bytes, and not less than 8 bytes.  The requirement
44
 *   for a string to take up 8 bytes is so that the scanner doesn't lose
45
 *   synch and mistake a string for an integer value.
46
 *
47
 * The first byte of the formatted string depends on if the integer is
48
 * an enum value (0xff) or an alias (0xfe).  If it is an alias then the
49
 * number refers to the word offset within the info map at which the
50
 * integer corresponding to the "target" value is stored.
51
 *
52
 * For example, consider the case of the string info map representing an
53
 * enumerated type of 'foo' (value 1) and 'bar' (value 2) and 'baz'
54
 * (alias for 'bar').  Note that string info maps are always little
55
 * endian.
56
 *
57
 * x01 x00 x00 x00   xff 'f' 'o' 'o'   x00 x00 x00 xff   x02 x00 x00 x00
58
 * xff 'b' 'a' 'r'   x00 x00 x00 xff   x03 x00 x00 x00   xfe 'b' 'a' 'z'
59
 * x00 x00 x00 xff
60
 *
61
 *
62
 * The operations that someone may want to perform with the map:
63
 *
64
 *   - look up if a string is valid (and not an alias)
65
 *   - look up the integer value for a enum 'nick'
66
 *   - look up the integer value for the target of an alias
67
 *   - look up an alias and convert it to its target string
68
 *   - look up the enum nick for a given value
69
 *
70
 * In order to look up if a string is valid, it is padded on either side
71
 * (as described) and scanned for in the array.  For example, you might
72
 * look for "foo":
73
 *
74
 *                   xff 'f' 'o' 'o'   x00 x00 x00 xff
75
 *
76
 * In order to look up the integer value for a nick, the string is padded
77
 * on either side and scanned for in the array, as above.  Instead of
78
 * merely succeeding, we look at the integer value to the left of the
79
 * match.  This is the enum value.
80
 *
81
 * In order to look up an alias and convert it to its target enum value,
82
 * the string is padded on either side (as described, with 0xfe) and
83
 * scanned for.  For example, you might look for "baz":
84
 *
85
 *                   xfe 'b' 'a' 'z'  x00 x00 x00 xff
86
 *
87
 * The integer immediately preceding the match then contains the offset
88
 * of the integer value of the target.  In our example, that's '3'.
89
 * This index is dereferenced to find the enum value of '2'.
90
 *
91
 * To convert the alias to its target string, 5 bytes just need to be
92
 * added past the start of the integer value to find the start of the
93
 * string.
94
 *
95
 * To look up the enum nick for a given value, the value is searched for
96
 * in the array.  To ensure that the value isn't matching the inside of a
97
 * string, we must check that it is either the first item in the array or
98
 * immediately preceded by the byte 0xff.  It must also be immediately
99
 * followed by the byte 0xff.
100
 *
101
 * Because strings always take up a minimum of 2 words, because 0xff or
102
 * 0xfe never appear inside of a utf-8 string and because no two integer
103
 * values ever appear in sequence, the only way we can have the
104
 * sequence:
105
 *
106
 *     xff __ __ __ __ xff (or 0xfe)
107
 *
108
 * is in the event of an integer nested between two strings.
109
 *
110
 * For implementation simplicity/efficiency, strings may not be more
111
 * than 65 characters in length (ie: 17 32bit words after padding).
112
 *
113
 * In the event that we are doing <choices> (ie: not an enum type) then
114
 * the value of each choice is set to zero and ignored.
115
 */
116
117
0
#define STRINFO_MAX_WORDS   17
118
G_GNUC_UNUSED static guint
119
strinfo_string_to_words (const gchar *string,
120
                         guint32     *words,
121
                         gboolean     alias)
122
0
{
123
0
  guint n_words;
124
0
  gsize size;
125
126
0
  size = strlen (string);
127
128
0
  n_words = MAX (2, (size + 6) >> 2);
129
130
0
  if (n_words > STRINFO_MAX_WORDS)
131
0
    return FALSE;
132
133
0
  words[0] = GUINT32_TO_LE (alias ? 0xfe : 0xff);
134
0
  words[n_words - 1] = GUINT32_TO_BE (0xff);
135
0
  memcpy (((gchar *) words) + 1, string, size + 1);
136
137
0
  return n_words;
138
0
}
Unexecuted instantiation: gsettings.c:strinfo_string_to_words
Unexecuted instantiation: gsettingsschema.c:strinfo_string_to_words
139
140
G_GNUC_UNUSED static gint
141
strinfo_scan (const guint32 *strinfo,
142
              guint          length,
143
              const guint32 *words,
144
              guint          n_words)
145
0
{
146
0
  guint i = 0;
147
148
0
  if (length < n_words)
149
0
    return -1;
150
151
0
  while (i <= length - n_words)
152
0
    {
153
0
      guint j = 0;
154
155
0
      for (j = 0; j < n_words; j++)
156
0
        if (strinfo[i + j] != words[j])
157
0
          break;
158
159
0
      if (j == n_words)
160
0
        return i;   /* match */
161
162
      /* skip at least one word, continue */
163
0
      i += j ? j : 1;
164
0
    }
165
166
0
  return -1;
167
0
}
Unexecuted instantiation: gsettings.c:strinfo_scan
Unexecuted instantiation: gsettingsschema.c:strinfo_scan
168
169
G_GNUC_UNUSED static gint
170
strinfo_find_string (const guint32 *strinfo,
171
                     guint          length,
172
                     const gchar   *string,
173
                     gboolean       alias)
174
0
{
175
0
  guint32 words[STRINFO_MAX_WORDS];
176
0
  guint n_words;
177
178
0
  if (length == 0)
179
0
    return -1;
180
181
0
  n_words = strinfo_string_to_words (string, words, alias);
182
183
0
  return strinfo_scan (strinfo + 1, length - 1, words, n_words);
184
0
}
Unexecuted instantiation: gsettings.c:strinfo_find_string
Unexecuted instantiation: gsettingsschema.c:strinfo_find_string
185
186
G_GNUC_UNUSED static gint
187
strinfo_find_integer (const guint32 *strinfo,
188
                      guint          length,
189
                      guint32        value)
190
0
{
191
0
  guint i;
192
193
0
  for (i = 0; i < length; i++)
194
0
    if (strinfo[i] == GUINT32_TO_LE (value))
195
0
      {
196
0
        const guchar *charinfo = (const guchar *) &strinfo[i];
197
198
        /* make sure it has 0xff on either side */
199
0
        if ((i == 0 || charinfo[-1] == 0xff) && charinfo[4] == 0xff)
200
0
          return i;
201
0
      }
202
203
0
  return -1;
204
0
}
Unexecuted instantiation: gsettings.c:strinfo_find_integer
Unexecuted instantiation: gsettingsschema.c:strinfo_find_integer
205
206
G_GNUC_UNUSED static gboolean
207
strinfo_is_string_valid (const guint32 *strinfo,
208
                         guint          length,
209
                         const gchar   *string)
210
0
{
211
0
  return strinfo_find_string (strinfo, length, string, FALSE) != -1;
212
0
}
Unexecuted instantiation: gsettings.c:strinfo_is_string_valid
Unexecuted instantiation: gsettingsschema.c:strinfo_is_string_valid
213
214
G_GNUC_UNUSED static gboolean
215
strinfo_enum_from_string (const guint32 *strinfo,
216
                          guint          length,
217
                          const gchar   *string,
218
                          guint         *result)
219
0
{
220
0
  gint index;
221
222
0
  index = strinfo_find_string (strinfo, length, string, FALSE);
223
224
0
  if (index < 0)
225
0
    return FALSE;
226
227
0
  *result = GUINT32_FROM_LE (strinfo[index]);
228
0
  return TRUE;
229
0
}
Unexecuted instantiation: gsettings.c:strinfo_enum_from_string
Unexecuted instantiation: gsettingsschema.c:strinfo_enum_from_string
230
231
G_GNUC_UNUSED static const gchar *
232
strinfo_string_from_enum (const guint32 *strinfo,
233
                          guint          length,
234
                          guint          value)
235
0
{
236
0
  gint index;
237
238
0
  index = strinfo_find_integer (strinfo, length, value);
239
240
0
  if (index < 0)
241
0
    return NULL;
242
243
0
  return 1 + (const gchar *) &strinfo[index + 1];
244
0
}
Unexecuted instantiation: gsettings.c:strinfo_string_from_enum
Unexecuted instantiation: gsettingsschema.c:strinfo_string_from_enum
245
246
G_GNUC_UNUSED static const gchar *
247
strinfo_string_from_alias (const guint32 *strinfo,
248
                           guint          length,
249
                           const gchar   *alias)
250
0
{
251
0
  gint index;
252
253
0
  index = strinfo_find_string (strinfo, length, alias, TRUE);
254
255
0
  if (index < 0)
256
0
    return NULL;
257
258
0
  return 1 + (const gchar *) &strinfo[GUINT32_TO_LE (strinfo[index]) + 1];
259
0
}
Unexecuted instantiation: gsettings.c:strinfo_string_from_alias
Unexecuted instantiation: gsettingsschema.c:strinfo_string_from_alias
260
261
G_GNUC_UNUSED static GVariant *
262
strinfo_enumerate (const guint32 *strinfo,
263
                   guint          length)
264
0
{
265
0
  GVariantBuilder builder;
266
0
  const gchar *ptr, *end;
267
268
0
  ptr = (gpointer) strinfo;
269
0
  end = ptr + 4 * length;
270
271
0
  ptr += 4;
272
273
0
  g_variant_builder_init (&builder, G_VARIANT_TYPE_STRING_ARRAY);
274
275
0
  while (ptr < end)
276
0
    {
277
      /* don't include aliases */
278
0
      if (*ptr == '\xff')
279
0
        g_variant_builder_add (&builder, "s", ptr + 1);
280
281
      /* find the end of this string */
282
0
      ptr = memchr (ptr, '\xff', end - ptr);
283
0
      g_assert (ptr != NULL);
284
285
      /* skip over the int to the next string */
286
0
      ptr += 5;
287
0
    }
288
289
0
  return g_variant_builder_end (&builder);
290
0
}
Unexecuted instantiation: gsettings.c:strinfo_enumerate
Unexecuted instantiation: gsettingsschema.c:strinfo_enumerate
291
292
G_GNUC_UNUSED static void
293
strinfo_builder_append_item (GString     *builder,
294
                             const gchar *string,
295
                             guint        value)
296
0
{
297
0
  guint32 words[STRINFO_MAX_WORDS];
298
0
  guint n_words;
299
0
300
0
  value = GUINT32_TO_LE (value);
301
0
302
0
  n_words = strinfo_string_to_words (string, words, FALSE);
303
0
  g_string_append_len (builder, (void *) &value, sizeof value);
304
0
  g_string_append_len (builder, (void *) words, 4 * n_words);
305
0
}
Unexecuted instantiation: gsettings.c:strinfo_builder_append_item
Unexecuted instantiation: gsettingsschema.c:strinfo_builder_append_item
306
307
G_GNUC_UNUSED static gboolean
308
strinfo_builder_append_alias (GString     *builder,
309
                              const gchar *alias,
310
                              const gchar *target)
311
0
{
312
0
  guint32 words[STRINFO_MAX_WORDS];
313
0
  guint n_words;
314
0
  guint value;
315
0
  gint index;
316
0
317
0
  index = strinfo_find_string ((const guint32 *) builder->str,
318
0
                               builder->len / 4, target, FALSE);
319
0
320
0
  if (index == -1)
321
0
    return FALSE;
322
0
323
0
  value = GUINT32_TO_LE (index);
324
0
325
0
  n_words = strinfo_string_to_words (alias, words, TRUE);
326
0
  g_string_append_len (builder, (void *) &value, sizeof value);
327
0
  g_string_append_len (builder, (void *) words, 4 * n_words);
328
0
329
0
  return TRUE;
330
0
}
Unexecuted instantiation: gsettings.c:strinfo_builder_append_alias
Unexecuted instantiation: gsettingsschema.c:strinfo_builder_append_alias
331
332
G_GNUC_UNUSED static gboolean
333
strinfo_builder_contains (GString     *builder,
334
                          const gchar *string)
335
0
{
336
0
  return strinfo_find_string ((const guint32 *) builder->str,
337
0
                              builder->len / 4, string, FALSE) != -1 ||
338
0
         strinfo_find_string ((const guint32 *) builder->str,
339
0
                              builder->len / 4, string, TRUE) != -1;
340
0
}
Unexecuted instantiation: gsettings.c:strinfo_builder_contains
Unexecuted instantiation: gsettingsschema.c:strinfo_builder_contains
341
342
G_GNUC_UNUSED static gboolean
343
strinfo_builder_contains_value (GString *builder,
344
                                guint    value)
345
0
{
346
0
  return strinfo_string_from_enum ((const guint32 *) builder->str,
347
0
                                   builder->len / 4, value) != NULL;
348
0
}
Unexecuted instantiation: gsettings.c:strinfo_builder_contains_value
Unexecuted instantiation: gsettingsschema.c:strinfo_builder_contains_value