Coverage Report

Created: 2025-12-03 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libyang/src/dict.c
Line
Count
Source
1
/**
2
 * @file dict.c
3
 * @author Radek Krejci <rkrejci@cesnet.cz>
4
 * @author Michal Vasko <mvasko@cesnet.cz>
5
 * @brief libyang dictionary for storing strings
6
 *
7
 * Copyright (c) 2015 - 2023 CESNET, z.s.p.o.
8
 *
9
 * This source code is licensed under BSD 3-Clause License (the "License").
10
 * You may not use this file except in compliance with the License.
11
 * You may obtain a copy of the License at
12
 *
13
 *     https://opensource.org/licenses/BSD-3-Clause
14
 */
15
16
#include "dict.h"
17
18
#include <assert.h>
19
#include <pthread.h>
20
#include <stdint.h>
21
#include <stdlib.h>
22
#include <string.h>
23
24
#include "compat.h"
25
#include "log.h"
26
#include "ly_common.h"
27
28
/* starting size of the dictionary */
29
44.5k
#define LYDICT_MIN_SIZE 1024
30
31
/**
32
 * @brief Comparison callback for dictionary's hash table
33
 *
34
 * Implementation of ::lyht_value_equal_cb.
35
 */
36
static ly_bool
37
lydict_val_eq(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *cb_data)
38
40.7M
{
39
40.7M
    const char *str1, *str2;
40
40.7M
    size_t *len1;
41
42
40.7M
    str1 = ((struct ly_dict_rec *)val1_p)->value;
43
40.7M
    str2 = ((struct ly_dict_rec *)val2_p)->value;
44
40.7M
    len1 = cb_data;
45
46
40.7M
    if (!strncmp(str1, str2, *len1) && !str2[*len1]) {
47
40.7M
        return 1;
48
40.7M
    }
49
50
571
    return 0;
51
40.7M
}
52
53
void
54
lydict_init(struct ly_dict *dict)
55
44.5k
{
56
44.5k
    LY_CHECK_ARG_RET(NULL, dict, );
57
58
44.5k
    dict->hash_tab = lyht_new(LYDICT_MIN_SIZE, sizeof(struct ly_dict_rec), lydict_val_eq, NULL, 1);
59
44.5k
    LY_CHECK_ERR_RET(!dict->hash_tab, LOGINT(NULL), );
60
44.5k
    pthread_mutex_init(&dict->lock, NULL);
61
44.5k
}
62
63
void
64
lydict_clean(struct ly_dict *dict)
65
44.5k
{
66
44.5k
    struct ly_dict_rec *dict_rec = NULL;
67
44.5k
    struct ly_ht_rec *rec = NULL;
68
44.5k
    uint32_t hlist_idx;
69
44.5k
    uint32_t rec_idx;
70
71
44.5k
    if (!dict) {
72
0
        return;
73
0
    }
74
75
45.5M
    LYHT_ITER_ALL_RECS(dict->hash_tab, hlist_idx, rec_idx, rec) {
76
        /*
77
         * this should not happen, all records inserted into
78
         * dictionary are supposed to be removed using lydict_remove()
79
         * before calling lydict_clean()
80
         */
81
0
        dict_rec = (struct ly_dict_rec *)rec->val;
82
0
        LOGWRN(NULL, "String \"%s\" not freed from the dictionary, refcount %" PRIu32 ".", dict_rec->value, dict_rec->refcount);
83
        /* if record wasn't removed before free string allocated for that record */
84
#ifdef NDEBUG
85
        free(dict_rec->value);
86
#endif
87
0
    }
88
89
    /* free table and destroy mutex */
90
44.5k
    lyht_free(dict->hash_tab, NULL);
91
44.5k
    pthread_mutex_destroy(&dict->lock);
92
44.5k
}
93
94
static ly_bool
95
lydict_resize_val_eq(void *val1_p, void *val2_p, ly_bool mod, void *UNUSED(cb_data))
96
4.19M
{
97
4.19M
    const char *str1, *str2;
98
99
4.19M
    LY_CHECK_ARG_RET(NULL, val1_p, val2_p, 0);
100
101
4.19M
    str1 = ((struct ly_dict_rec *)val1_p)->value;
102
4.19M
    str2 = ((struct ly_dict_rec *)val2_p)->value;
103
104
4.19M
    if (mod) {
105
        /* used when inserting new values */
106
14
        if (strcmp(str1, str2) == 0) {
107
0
            return 1;
108
0
        }
109
4.19M
    } else {
110
        /* used when finding the original value again in the resized table */
111
4.19M
        if (str1 == str2) {
112
4.19M
            return 1;
113
4.19M
        }
114
4.19M
    }
115
116
15
    return 0;
117
4.19M
}
118
119
/**
120
 * @brief Remove a string from the dictionary.
121
 *
122
 * @param[in] ctx Context to use.
123
 * @param[in] dict Dictionary to remove from.
124
 * @param[in] value Value to remove.
125
 * @return LY_SUCCESS on success, LY_ERR value on error.
126
 */
127
static LY_ERR
128
_lydict_remove(const struct ly_ctx *ctx, struct ly_dict *dict, const char *value)
129
22.4M
{
130
22.4M
    LY_ERR ret = LY_SUCCESS;
131
22.4M
    size_t len;
132
22.4M
    uint32_t hash;
133
22.4M
    struct ly_dict_rec rec, *match = NULL;
134
22.4M
    char *val_p;
135
136
22.4M
    LOGDBG(LY_LDGDICT, "removing \"%s\"", value);
137
138
22.4M
    len = strlen(value);
139
22.4M
    hash = lyht_hash(value, len);
140
141
    /* create record for lyht_find call */
142
22.4M
    rec.value = (char *)value;
143
22.4M
    rec.refcount = 0;
144
145
    /* set len as data for compare callback */
146
22.4M
    lyht_set_cb_data(dict->hash_tab, (void *)&len);
147
    /* check if value is already inserted */
148
22.4M
    ret = lyht_find(dict->hash_tab, &rec, hash, (void **)&match);
149
150
22.4M
    if (ret == LY_SUCCESS) {
151
22.4M
        LY_CHECK_ERR_GOTO(!match, LOGINT(ctx), cleanup);
152
153
        /* if value is already in dictionary, decrement reference counter */
154
22.4M
        match->refcount--;
155
22.4M
        if (match->refcount == 0) {
156
            /*
157
             * remove record
158
             * save pointer to stored string before lyht_remove to
159
             * free it after it is removed from hash table
160
             */
161
9.40M
            val_p = match->value;
162
9.40M
            ret = lyht_remove_with_resize_cb(dict->hash_tab, &rec, hash, lydict_resize_val_eq);
163
9.40M
            free(val_p);
164
9.40M
            LY_CHECK_ERR_GOTO(ret, LOGINT(ctx), cleanup);
165
9.40M
        }
166
22.4M
    } else if (ret == LY_ENOTFOUND) {
167
0
        LOGERR(ctx, LY_ENOTFOUND, "Value \"%s\" was not found in the dictionary.", value);
168
0
    } else {
169
0
        LOGINT(ctx);
170
0
    }
171
172
22.4M
cleanup:
173
22.4M
    return ret;
174
22.4M
}
175
176
LY_ERR
177
lysdict_remove(const struct ly_ctx *ctx, const char *value)
178
40.5M
{
179
40.5M
    if (!ctx || !value) {
180
18.1M
        return LY_SUCCESS;
181
18.1M
    }
182
183
22.4M
    return _lydict_remove(ctx, (struct ly_dict *)&ctx->dict, value);
184
40.5M
}
185
186
LIBYANG_API_DEF LY_ERR
187
lydict_remove(const struct ly_ctx *ctx, const char *value)
188
57.0k
{
189
57.0k
    LY_ERR ret;
190
57.0k
    struct ly_dict *dict;
191
192
57.0k
    if (!ctx || !value) {
193
15.8k
        return LY_SUCCESS;
194
15.8k
    }
195
196
41.1k
    dict = ly_ctx_data_dict_get(ctx);
197
198
41.1k
    pthread_mutex_lock(&dict->lock);
199
41.1k
    ret = _lydict_remove(ctx, dict, value);
200
41.1k
    pthread_mutex_unlock(&dict->lock);
201
202
41.1k
    return ret;
203
57.0k
}
204
205
/**
206
 * @brief Insert a new string into the dictionary.
207
 *
208
 * @param[in] dict Dictionary to insert into.
209
 * @param[in] value Value to insert.
210
 * @param[in] len Length of @p value.
211
 * @param[in] zerocopy Whether to use the value directly or make a copy.
212
 * @param[out] str_p Optional pointer to the inserted string.
213
 * @return LY_SUCCESS on success, LY_ERR value on error.
214
 */
215
static LY_ERR
216
dict_insert(struct ly_dict *dict, char *value, size_t len, ly_bool zerocopy, const char **str_p)
217
18.2M
{
218
18.2M
    LY_ERR ret = LY_SUCCESS;
219
18.2M
    struct ly_dict_rec *match = NULL, rec;
220
18.2M
    uint32_t hash;
221
222
18.2M
    LOGDBG(LY_LDGDICT, "inserting \"%.*s\"", (int)len, value);
223
224
18.2M
    hash = lyht_hash(value, len);
225
    /* set len as data for compare callback */
226
18.2M
    lyht_set_cb_data(dict->hash_tab, (void *)&len);
227
    /* create record for lyht_insert */
228
18.2M
    rec.value = value;
229
18.2M
    rec.refcount = 1;
230
231
18.2M
    ret = lyht_insert_with_resize_cb(dict->hash_tab, (void *)&rec, hash, lydict_resize_val_eq, (void **)&match);
232
18.2M
    if (ret == LY_EEXIST) {
233
8.85M
        match->refcount++;
234
8.85M
        if (zerocopy) {
235
261k
            free(value);
236
261k
        }
237
8.85M
        ret = LY_SUCCESS;
238
9.40M
    } else if (ret == LY_SUCCESS) {
239
9.40M
        if (!zerocopy) {
240
            /*
241
             * allocate string for new record
242
             * record is already inserted in hash table
243
             */
244
5.98M
            match->value = malloc(sizeof *match->value * (len + 1));
245
5.98M
            LY_CHECK_ERR_RET(!match->value, LOGMEM(NULL), LY_EMEM);
246
5.98M
            if (len) {
247
5.98M
                memcpy(match->value, value, len);
248
5.98M
            }
249
5.98M
            match->value[len] = '\0';
250
5.98M
        }
251
9.40M
    } else {
252
        /* lyht_insert returned error */
253
0
        if (zerocopy) {
254
0
            free(value);
255
0
        }
256
0
        return ret;
257
0
    }
258
259
18.2M
    *str_p = match->value;
260
261
18.2M
    return ret;
262
18.2M
}
263
264
LY_ERR
265
lysdict_insert(const struct ly_ctx *ctx, const char *value, size_t len, const char **str_p)
266
14.5M
{
267
14.5M
    if (!value) {
268
0
        *str_p = NULL;
269
0
        return LY_SUCCESS;
270
0
    }
271
272
14.5M
    if (!len) {
273
2.31M
        len = strlen(value);
274
2.31M
    }
275
276
    /* no need to lock dict lock, because we are inserting into a schema dict,
277
     * which is thread safe unlike data parsing */
278
14.5M
    return dict_insert((struct ly_dict *)&ctx->dict, (char *)value, len, 0, str_p);
279
14.5M
}
280
281
LIBYANG_API_DEF LY_ERR
282
lydict_insert(const struct ly_ctx *ctx, const char *value, size_t len, const char **str_p)
283
26.7k
{
284
26.7k
    LY_ERR rc;
285
26.7k
    struct ly_dict *dict;
286
287
26.7k
    LY_CHECK_ARG_RET(ctx, ctx, str_p, LY_EINVAL);
288
289
26.7k
    if (!value) {
290
0
        *str_p = NULL;
291
0
        return LY_SUCCESS;
292
0
    }
293
294
26.7k
    if (!len) {
295
6
        len = strlen(value);
296
6
    }
297
298
26.7k
    dict = ly_ctx_data_dict_get(ctx);
299
300
26.7k
    pthread_mutex_lock(&dict->lock);
301
26.7k
    rc = dict_insert(dict, (char *)value, len, 0, str_p);
302
26.7k
    pthread_mutex_unlock(&dict->lock);
303
304
26.7k
    return rc;
305
26.7k
}
306
307
LY_ERR
308
lysdict_insert_zc(const struct ly_ctx *ctx, char *value, const char **str_p)
309
3.66M
{
310
3.66M
    if (!value) {
311
0
        *str_p = NULL;
312
0
        return LY_SUCCESS;
313
0
    }
314
315
3.66M
    return dict_insert((struct ly_dict *)&ctx->dict, value, strlen(value), 1, str_p);
316
3.66M
}
317
318
LIBYANG_API_DEF LY_ERR
319
lydict_insert_zc(const struct ly_ctx *ctx, char *value, const char **str_p)
320
14.3k
{
321
14.3k
    LY_ERR rc;
322
14.3k
    struct ly_dict *dict;
323
324
14.3k
    LY_CHECK_ARG_RET(ctx, ctx, str_p, LY_EINVAL);
325
326
14.3k
    if (!value) {
327
0
        *str_p = NULL;
328
0
        return LY_SUCCESS;
329
0
    }
330
331
14.3k
    dict = ly_ctx_data_dict_get(ctx);
332
333
14.3k
    pthread_mutex_lock(&dict->lock);
334
14.3k
    rc = dict_insert(dict, value, strlen(value), 1, str_p);
335
14.3k
    pthread_mutex_unlock(&dict->lock);
336
337
14.3k
    return rc;
338
14.3k
}
339
340
static LY_ERR
341
dict_dup(struct ly_dict *dict, char *value, const char **str_p)
342
4.19M
{
343
4.19M
    LY_ERR ret = LY_SUCCESS;
344
4.19M
    struct ly_dict_rec *match = NULL, rec;
345
4.19M
    uint32_t hash;
346
347
    /* set new callback to only compare memory addresses */
348
4.19M
    lyht_value_equal_cb prev = lyht_set_cb(dict->hash_tab, lydict_resize_val_eq);
349
350
4.19M
    LOGDBG(LY_LDGDICT, "duplicating %s", value);
351
4.19M
    hash = lyht_hash(value, strlen(value));
352
4.19M
    rec.value = value;
353
354
4.19M
    ret = lyht_find(dict->hash_tab, (void *)&rec, hash, (void **)&match);
355
4.19M
    if (ret == LY_SUCCESS) {
356
        /* record found, increase refcount */
357
4.19M
        match->refcount++;
358
4.19M
        *str_p = match->value;
359
4.19M
    }
360
361
    /* restore callback */
362
4.19M
    lyht_set_cb(dict->hash_tab, prev);
363
364
4.19M
    return ret;
365
4.19M
}
366
367
LY_ERR
368
lysdict_dup(const struct ly_ctx *ctx, const char *value, const char **str_p)
369
7.52M
{
370
7.52M
    if (!value) {
371
3.32M
        *str_p = NULL;
372
3.32M
        return LY_SUCCESS;
373
3.32M
    }
374
375
4.19M
    return dict_dup((struct ly_dict *)&ctx->dict, (char *)value, str_p);
376
7.52M
}
377
378
LIBYANG_API_DEF LY_ERR
379
lydict_dup(const struct ly_ctx *ctx, const char *value, const char **str_p)
380
0
{
381
0
    LY_ERR rc;
382
0
    struct ly_dict *dict;
383
384
0
    LY_CHECK_ARG_RET(ctx, ctx, str_p, LY_EINVAL);
385
386
0
    if (!value) {
387
0
        *str_p = NULL;
388
0
        return LY_SUCCESS;
389
0
    }
390
391
0
    dict = ly_ctx_data_dict_get(ctx);
392
393
0
    pthread_mutex_lock(&dict->lock);
394
0
    rc = dict_dup(dict, (char *)value, str_p);
395
0
    pthread_mutex_unlock(&dict->lock);
396
397
0
    return rc;
398
0
}