Coverage Report

Created: 2026-09-03 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/jansson/src/strconv.c
Line
Count
Source
1
#include "jansson_private.h"
2
#include "strbuffer.h"
3
#include <assert.h>
4
#include <errno.h>
5
#include <math.h>
6
#include <stdio.h>
7
#include <string.h>
8
9
/* need jansson_private_config.h to get the correct snprintf */
10
#ifdef HAVE_CONFIG_H
11
#include <jansson_private_config.h>
12
#endif
13
14
/*
15
  - This code assumes that the decimal separator is exactly one
16
    character.
17
18
  - If setlocale() is called by another thread between the call to
19
    get_decimal_point() and the call to sprintf() or strtod(), the
20
    result may be wrong. setlocale() is not thread-safe and should
21
    not be used this way. Multi-threaded programs should use
22
    uselocale() instead.
23
*/
24
31.2k
static char get_decimal_point() {
25
31.2k
    char buf[3];
26
31.2k
    sprintf(buf, "%#.0f", 1.0); // "1." in the current locale
27
31.2k
    return buf[1];
28
31.2k
}
29
30
31.2k
static void to_locale(strbuffer_t *strbuffer) {
31
31.2k
    char point;
32
31.2k
    char *pos;
33
34
31.2k
    point = get_decimal_point();
35
31.2k
    if (point == '.') {
36
        /* No conversion needed */
37
31.2k
        return;
38
31.2k
    }
39
40
0
    pos = strchr(strbuffer->value, '.');
41
0
    if (pos)
42
0
        *pos = point;
43
0
}
44
45
31.2k
int jsonp_strtod(strbuffer_t *strbuffer, double *out) {
46
31.2k
    double value;
47
31.2k
    char *end;
48
49
31.2k
    to_locale(strbuffer);
50
51
31.2k
    errno = 0;
52
31.2k
    value = strtod(strbuffer->value, &end);
53
31.2k
    assert(end == strbuffer->value + strbuffer->length);
54
55
31.2k
    if ((value == HUGE_VAL || value == -HUGE_VAL) && errno == ERANGE) {
56
        /* Overflow */
57
9
        return -1;
58
9
    }
59
60
31.2k
    *out = value;
61
31.2k
    return 0;
62
31.2k
}
63
64
#if DTOA_ENABLED
65
/* see dtoa.c */
66
char *dtoa_r(double dd, int mode, int ndigits, int *decpt, int *sign, char **rve,
67
             char *buf, size_t blen);
68
69
34.4k
int jsonp_dtostr(char *buffer, size_t size, double value, int precision) {
70
    /* adapted from `format_float_short()` in
71
     * https://github.com/python/cpython/blob/2cf18a44303b6d84faa8ecffaecc427b53ae121e/Python/pystrtod.c#L969
72
     */
73
34.4k
    char digits[25];
74
34.4k
    char *digits_end;
75
34.4k
    int mode = precision == 0 ? 0 : 2;
76
34.4k
    int decpt, sign, exp_len, exp = 0, use_exp = 0;
77
34.4k
    int digits_len, vdigits_start, vdigits_end;
78
34.4k
    char *p;
79
80
34.4k
    if (dtoa_r(value, mode, precision, &decpt, &sign, &digits_end, digits, 25) == NULL) {
81
        // digits is too short => should not happen
82
0
        return -1;
83
0
    }
84
85
34.4k
    digits_len = digits_end - digits;
86
34.4k
    if (decpt <= -4 || decpt > 16) {
87
26.7k
        use_exp = 1;
88
26.7k
        exp = decpt - 1;
89
26.7k
        decpt = 1;
90
26.7k
    }
91
92
34.4k
    vdigits_start = decpt <= 0 ? decpt - 1 : 0;
93
34.4k
    vdigits_end = digits_len;
94
34.4k
    if (!use_exp) {
95
        /* decpt + 1 to add ".0" if value is an integer */
96
7.74k
        vdigits_end = vdigits_end > decpt ? vdigits_end : decpt + 1;
97
26.7k
    } else {
98
26.7k
        vdigits_end = vdigits_end > decpt ? vdigits_end : decpt;
99
26.7k
    }
100
101
34.4k
    if (
102
        /* sign, decimal point and trailing 0 byte */
103
34.4k
        (size_t)(3 +
104
105
                 /* total digit count (including zero padding on both sides) */
106
34.4k
                 (vdigits_end - vdigits_start) +
107
108
                 /* exponent "e+100", max 3 numerical digits */
109
34.4k
                 (use_exp ? 5 : 0)) > size) {
110
        /* buffer is too short */
111
0
        return -1;
112
0
    }
113
114
34.4k
    p = buffer;
115
34.4k
    if (sign == 1) {
116
17.9k
        *p++ = '-';
117
17.9k
    }
118
119
    /* note that exactly one of the three 'if' conditions is true,
120
      so we include exactly one decimal point */
121
    /* Zero padding on left of digit string */
122
34.4k
    if (decpt <= 0) {
123
331
        memset(p, '0', decpt - vdigits_start);
124
331
        p += decpt - vdigits_start;
125
331
        *p++ = '.';
126
331
        memset(p, '0', 0 - decpt);
127
331
        p += 0 - decpt;
128
34.1k
    } else {
129
34.1k
        memset(p, '0', 0 - vdigits_start);
130
34.1k
        p += 0 - vdigits_start;
131
34.1k
    }
132
133
    /* Digits, with included decimal point */
134
34.4k
    if (0 < decpt && decpt <= digits_len) {
135
32.6k
        strncpy(p, digits, decpt - 0);
136
32.6k
        p += decpt - 0;
137
32.6k
        *p++ = '.';
138
32.6k
        strncpy(p, digits + decpt, digits_len - decpt);
139
32.6k
        p += digits_len - decpt;
140
32.6k
    } else {
141
1.81k
        strncpy(p, digits, digits_len);
142
1.81k
        p += digits_len;
143
1.81k
    }
144
145
    /* And zeros on the right */
146
34.4k
    if (digits_len < decpt) {
147
1.48k
        memset(p, '0', decpt - digits_len);
148
1.48k
        p += decpt - digits_len;
149
1.48k
        *p++ = '.';
150
1.48k
        memset(p, '0', vdigits_end - decpt);
151
1.48k
        p += vdigits_end - decpt;
152
32.9k
    } else {
153
32.9k
        memset(p, '0', vdigits_end - digits_len);
154
32.9k
        p += vdigits_end - digits_len;
155
32.9k
    }
156
157
34.4k
    if (p[-1] == '.')
158
254
        p--;
159
160
34.4k
    if (use_exp) {
161
26.7k
        *p++ = 'e';
162
26.7k
        exp_len = sprintf(p, "%d", exp);
163
26.7k
        p += exp_len;
164
26.7k
    }
165
34.4k
    *p = '\0';
166
167
34.4k
    return (int)(p - buffer);
168
34.4k
}
169
#else /* DTOA_ENABLED == 0 */
170
static void from_locale(char *buffer) {
171
    char point;
172
    char *pos;
173
174
    point = get_decimal_point();
175
    if (point == '.') {
176
        /* No conversion needed */
177
        return;
178
    }
179
180
    pos = strchr(buffer, point);
181
    if (pos)
182
        *pos = '.';
183
}
184
185
int jsonp_dtostr(char *buffer, size_t size, double value, int precision) {
186
    int ret;
187
    char *start, *end;
188
    size_t length;
189
190
    if (precision == 0)
191
        precision = 17;
192
193
    ret = snprintf(buffer, size, "%.*g", precision, value);
194
    if (ret < 0)
195
        return -1;
196
197
    length = (size_t)ret;
198
    if (length >= size)
199
        return -1;
200
201
    from_locale(buffer);
202
203
    /* Make sure there's a dot or 'e' in the output. Otherwise
204
       a real is converted to an integer when decoding */
205
    if (strchr(buffer, '.') == NULL && strchr(buffer, 'e') == NULL) {
206
        if (length + 3 >= size) {
207
            /* No space to append ".0" */
208
            return -1;
209
        }
210
        buffer[length] = '.';
211
        buffer[length + 1] = '0';
212
        buffer[length + 2] = '\0';
213
        length += 2;
214
    }
215
216
    /* Remove leading '+' from positive exponent. Also remove leading
217
       zeros from exponents (added by some printf() implementations) */
218
    start = strchr(buffer, 'e');
219
    if (start) {
220
        start++;
221
        end = start + 1;
222
223
        if (*start == '-')
224
            start++;
225
226
        while (*end == '0')
227
            end++;
228
229
        if (end != start) {
230
            memmove(start, end, length - (size_t)(end - buffer));
231
            length -= (size_t)(end - start);
232
        }
233
    }
234
235
    return (int)length;
236
}
237
#endif