Coverage Report

Created: 2026-10-31 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Python/mystrtoul.c
Line
Count
Source
1
// strtol() and strtoul(), renamed to avoid conflicts.
2
//
3
// API:
4
//
5
// - PyOS_strtol(): convert string to C long integer.
6
// - PyOS_strtoul(): convert string to C unsigned long integer.
7
8
#include "Python.h"
9
#include "pycore_long.h"          // _PyLong_DigitValue
10
11
/* strtol and strtoul, renamed to avoid conflicts */
12
13
14
#ifdef HAVE_ERRNO_H
15
#  include <errno.h>              // errno
16
#endif
17
18
/* Static overflow check values for bases 2 through 36.
19
 * smallmax[base] is the largest unsigned long i such that
20
 * i * base doesn't overflow unsigned long.
21
 */
22
static const unsigned long smallmax[] = {
23
    0, /* bases 0 and 1 are invalid */
24
    0,
25
    ULONG_MAX / 2,
26
    ULONG_MAX / 3,
27
    ULONG_MAX / 4,
28
    ULONG_MAX / 5,
29
    ULONG_MAX / 6,
30
    ULONG_MAX / 7,
31
    ULONG_MAX / 8,
32
    ULONG_MAX / 9,
33
    ULONG_MAX / 10,
34
    ULONG_MAX / 11,
35
    ULONG_MAX / 12,
36
    ULONG_MAX / 13,
37
    ULONG_MAX / 14,
38
    ULONG_MAX / 15,
39
    ULONG_MAX / 16,
40
    ULONG_MAX / 17,
41
    ULONG_MAX / 18,
42
    ULONG_MAX / 19,
43
    ULONG_MAX / 20,
44
    ULONG_MAX / 21,
45
    ULONG_MAX / 22,
46
    ULONG_MAX / 23,
47
    ULONG_MAX / 24,
48
    ULONG_MAX / 25,
49
    ULONG_MAX / 26,
50
    ULONG_MAX / 27,
51
    ULONG_MAX / 28,
52
    ULONG_MAX / 29,
53
    ULONG_MAX / 30,
54
    ULONG_MAX / 31,
55
    ULONG_MAX / 32,
56
    ULONG_MAX / 33,
57
    ULONG_MAX / 34,
58
    ULONG_MAX / 35,
59
    ULONG_MAX / 36,
60
};
61
62
/* maximum digits that can't ever overflow for bases 2 through 36,
63
 * calculated by [int(math.floor(math.log(2**32, i))) for i in range(2, 37)].
64
 * Note that this is pessimistic if sizeof(long) > 4.
65
 */
66
#if SIZEOF_LONG == 4
67
static const int digitlimit[] = {
68
    0,  0, 32, 20, 16, 13, 12, 11, 10, 10,  /*  0 -  9 */
69
    9,  9,  8,  8,  8,  8,  8,  7,  7,  7,  /* 10 - 19 */
70
    7,  7,  7,  7,  6,  6,  6,  6,  6,  6,  /* 20 - 29 */
71
    6,  6,  6,  6,  6,  6,  6};             /* 30 - 36 */
72
#elif SIZEOF_LONG == 8
73
/* [int(math.floor(math.log(2**64, i))) for i in range(2, 37)] */
74
static const int digitlimit[] = {
75
         0,   0, 64, 40, 32, 27, 24, 22, 21, 20,  /*  0 -  9 */
76
    19,  18, 17, 17, 16, 16, 16, 15, 15, 15,  /* 10 - 19 */
77
    14,  14, 14, 14, 13, 13, 13, 13, 13, 13,  /* 20 - 29 */
78
    13,  12, 12, 12, 12, 12, 12};             /* 30 - 36 */
79
#else
80
#  error "Need table for SIZEOF_LONG"
81
#endif
82
83
/*
84
**      strtoul
85
**              This is a general purpose routine for converting
86
**              an ascii string to an integer in an arbitrary base.
87
**              Leading white space is ignored.  If 'base' is zero
88
**              it looks for a leading 0b, 0o or 0x to tell which
89
**              base.  If these are absent it defaults to 10.
90
**              Base must be 0 or between 2 and 36 (inclusive).
91
**              If 'ptr' is non-NULL it will contain a pointer to
92
**              the end of the scan.
93
**              Errors due to bad pointers will probably result in
94
**              exceptions - we don't check for them.
95
*/
96
unsigned long
97
PyOS_strtoul(const char *str, char **ptr, int base)
98
133k
{
99
133k
    unsigned long result = 0; /* return value of the function */
100
133k
    int c;             /* current input character */
101
133k
    int ovlimit;       /* required digits to overflow */
102
103
    /* skip leading white space */
104
133k
    while (*str && Py_ISSPACE(*str))
105
0
        ++str;
106
107
    /* check for leading 0b, 0o or 0x for auto-base or base 16 */
108
133k
    switch (base) {
109
133k
    case 0:             /* look for leading 0b, 0o or 0x */
110
133k
        if (*str == '0') {
111
36.7k
            ++str;
112
36.7k
            if (*str == 'x' || *str == 'X') {
113
                /* there must be at least one digit after 0x */
114
16.1k
                if (_PyLong_DigitValue[Py_CHARMASK(str[1])] >= 16) {
115
0
                    if (ptr)
116
0
                        *ptr = (char *)str;
117
0
                    return 0;
118
0
                }
119
16.1k
                ++str;
120
16.1k
                base = 16;
121
20.6k
            } else if (*str == 'o' || *str == 'O') {
122
                /* there must be at least one digit after 0o */
123
1.98k
                if (_PyLong_DigitValue[Py_CHARMASK(str[1])] >= 8) {
124
0
                    if (ptr)
125
0
                        *ptr = (char *)str;
126
0
                    return 0;
127
0
                }
128
1.98k
                ++str;
129
1.98k
                base = 8;
130
18.6k
            } else if (*str == 'b' || *str == 'B') {
131
                /* there must be at least one digit after 0b */
132
586
                if (_PyLong_DigitValue[Py_CHARMASK(str[1])] >= 2) {
133
0
                    if (ptr)
134
0
                        *ptr = (char *)str;
135
0
                    return 0;
136
0
                }
137
586
                ++str;
138
586
                base = 2;
139
18.0k
            } else {
140
                /* skip all zeroes... */
141
20.5k
                while (*str == '0')
142
2.47k
                    ++str;
143
18.0k
                while (Py_ISSPACE(*str))
144
0
                    ++str;
145
18.0k
                if (ptr)
146
18.0k
                    *ptr = (char *)str;
147
18.0k
                return 0;
148
18.0k
            }
149
36.7k
        }
150
96.7k
        else
151
96.7k
            base = 10;
152
115k
        break;
153
154
    /* even with explicit base, skip leading 0? prefix */
155
115k
    case 16:
156
0
        if (*str == '0') {
157
0
            ++str;
158
0
            if (*str == 'x' || *str == 'X') {
159
                /* there must be at least one digit after 0x */
160
0
                if (_PyLong_DigitValue[Py_CHARMASK(str[1])] >= 16) {
161
0
                    if (ptr)
162
0
                        *ptr = (char *)str;
163
0
                    return 0;
164
0
                }
165
0
                ++str;
166
0
            }
167
0
        }
168
0
        break;
169
0
    case 8:
170
0
        if (*str == '0') {
171
0
            ++str;
172
0
            if (*str == 'o' || *str == 'O') {
173
                /* there must be at least one digit after 0o */
174
0
                if (_PyLong_DigitValue[Py_CHARMASK(str[1])] >= 8) {
175
0
                    if (ptr)
176
0
                        *ptr = (char *)str;
177
0
                    return 0;
178
0
                }
179
0
                ++str;
180
0
            }
181
0
        }
182
0
        break;
183
0
    case 2:
184
0
        if(*str == '0') {
185
0
            ++str;
186
0
            if (*str == 'b' || *str == 'B') {
187
                /* there must be at least one digit after 0b */
188
0
                if (_PyLong_DigitValue[Py_CHARMASK(str[1])] >= 2) {
189
0
                    if (ptr)
190
0
                        *ptr = (char *)str;
191
0
                    return 0;
192
0
                }
193
0
                ++str;
194
0
            }
195
0
        }
196
0
        break;
197
133k
    }
198
199
    /* catch silly bases */
200
115k
    if (base < 2 || base > 36) {
201
0
        if (ptr)
202
0
            *ptr = (char *)str;
203
0
        return 0;
204
0
    }
205
206
    /* skip leading zeroes */
207
142k
    while (*str == '0')
208
27.3k
        ++str;
209
210
    /* base is guaranteed to be in [2, 36] at this point */
211
115k
    ovlimit = digitlimit[base];
212
213
    /* do the conversion until non-digit character encountered */
214
469k
    while ((c = _PyLong_DigitValue[Py_CHARMASK(*str)]) < base) {
215
358k
        if (ovlimit > 0) /* no overflow check required */
216
351k
            result = result * base + c;
217
6.75k
        else { /* requires overflow check */
218
6.75k
            unsigned long temp_result;
219
220
6.75k
            if (ovlimit < 0) /* guaranteed overflow */
221
1.84k
                goto overflowed;
222
223
            /* there could be an overflow */
224
            /* check overflow just from shifting */
225
4.91k
            if (result > smallmax[base])
226
1.95k
                goto overflowed;
227
228
2.95k
            result *= base;
229
230
            /* check overflow from the digit's value */
231
2.95k
            temp_result = result + c;
232
2.95k
            if (temp_result < result)
233
401
                goto overflowed;
234
235
2.55k
            result = temp_result;
236
2.55k
        }
237
238
354k
        ++str;
239
354k
        --ovlimit;
240
354k
    }
241
242
    /* set pointer to point to the last character scanned */
243
111k
    if (ptr)
244
111k
        *ptr = (char *)str;
245
246
111k
    return result;
247
248
4.20k
overflowed:
249
4.20k
    if (ptr) {
250
        /* spool through remaining digit characters */
251
103k
        while (_PyLong_DigitValue[Py_CHARMASK(*str)] < base)
252
98.9k
            ++str;
253
4.20k
        *ptr = (char *)str;
254
4.20k
    }
255
4.20k
    errno = ERANGE;
256
4.20k
    return (unsigned long)-1;
257
115k
}
258
259
/* Checking for overflow in PyOS_strtol is a PITA; see comments
260
 * about PY_ABS_LONG_MIN in longobject.c.
261
 */
262
0
#define PY_ABS_LONG_MIN         (0-(unsigned long)LONG_MIN)
263
264
long
265
PyOS_strtol(const char *str, char **ptr, int base)
266
96.7k
{
267
96.7k
    long result;
268
96.7k
    unsigned long uresult;
269
96.7k
    char sign;
270
271
96.7k
    while (*str && Py_ISSPACE(*str))
272
0
        str++;
273
274
96.7k
    sign = *str;
275
96.7k
    if (sign == '+' || sign == '-')
276
0
        str++;
277
278
96.7k
    uresult = PyOS_strtoul(str, ptr, base);
279
280
96.7k
    if (uresult <= (unsigned long)LONG_MAX) {
281
93.2k
        result = (long)uresult;
282
93.2k
        if (sign == '-')
283
0
            result = -result;
284
93.2k
    }
285
3.50k
    else if (sign == '-' && uresult == PY_ABS_LONG_MIN) {
286
0
        result = LONG_MIN;
287
0
    }
288
3.50k
    else {
289
3.50k
        errno = ERANGE;
290
        result = LONG_MAX;
291
3.50k
    }
292
96.7k
    return result;
293
96.7k
}