Skip to content

Commit 7cd2c02

Browse files
committed
gh-130599: Fix data race in long_from_non_binary_base
The global initialization wasn't thread-safe with free threading or per-interpreter GIL. Use a `_PyOnceFlag` to initialize the entries in a thread-safe manner.
1 parent 5c8e870 commit 7cd2c02

File tree

2 files changed

+44
-34
lines changed

2 files changed

+44
-34
lines changed

Objects/longobject.c

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "pycore_initconfig.h" // _PyStatus_OK()
88
#include "pycore_call.h" // _PyObject_MakeTpCall
99
#include "pycore_freelist.h" // _Py_FREELIST_FREE, _Py_FREELIST_POP
10+
#include "pycore_lock.h" // _PyOnceFlag_CallOnce
1011
#include "pycore_long.h" // _Py_SmallInts
1112
#include "pycore_object.h" // _PyObject_Init()
1213
#include "pycore_runtime.h" // _PY_NSMALLPOSINTS
@@ -2732,6 +2733,36 @@ pylong_int_from_string(const char *start, const char *end, PyLongObject **res)
27322733
}
27332734
#endif /* WITH_PYLONG_MODULE */
27342735

2736+
static struct non_binary_base_state {
2737+
double log_base_BASE;
2738+
twodigits convmultmax_base;
2739+
int convwidth_base;
2740+
_PyOnceFlag once;
2741+
} state[37];
2742+
2743+
static int
2744+
init_non_binary_base(void *arg)
2745+
{
2746+
int base = *(int *)arg;
2747+
twodigits convmax = base;
2748+
int i = 1;
2749+
2750+
state[base].log_base_BASE = (log((double)base) /
2751+
log((double)PyLong_BASE));
2752+
for (;;) {
2753+
twodigits next = convmax * base;
2754+
if (next > PyLong_BASE) {
2755+
break;
2756+
}
2757+
convmax = next;
2758+
++i;
2759+
}
2760+
state[base].convmultmax_base = convmax;
2761+
assert(i > 0);
2762+
state[base].convwidth_base = i;
2763+
return 0;
2764+
}
2765+
27352766
/***
27362767
long_from_non_binary_base: parameters and return values are the same as
27372768
long_from_binary_base.
@@ -2748,26 +2779,26 @@ case number of Python digits needed to hold it is the smallest integer n s.t.
27482779
BASE**n >= B**N [taking logs to base BASE]
27492780
n >= log(B**N)/log(BASE) = N * log(B)/log(BASE)
27502781
2751-
The static array log_base_BASE[base] == log(base)/log(BASE) so we can compute
2782+
The value log_base_BASE == log(base)/log(BASE) so we can compute
27522783
this quickly. A Python int with that much space is reserved near the start,
27532784
and the result is computed into it.
27542785
27552786
The input string is actually treated as being in base base**i (i.e., i digits
2756-
are processed at a time), where two more static arrays hold:
2787+
are processed at a time), where two more values:
27572788
2758-
convwidth_base[base] = the largest integer i such that base**i <= BASE
2759-
convmultmax_base[base] = base ** convwidth_base[base]
2789+
convwidth_base = the largest integer i such that base**i <= BASE
2790+
convmultmax_base = base ** convwidth_base
27602791
27612792
The first of these is the largest i such that i consecutive input digits
27622793
must fit in a single Python digit. The second is effectively the input
27632794
base we're really using.
27642795
27652796
Viewing the input as a sequence <c0, c1, ..., c_n-1> of digits in base
2766-
convmultmax_base[base], the result is "simply"
2797+
convmultmax_base, the result is "simply"
27672798
27682799
(((c0*B + c1)*B + c2)*B + c3)*B + ... ))) + c_n-1
27692800
2770-
where B = convmultmax_base[base].
2801+
where B = convmultmax_base.
27712802
27722803
Error analysis: as above, the number of Python digits `n` needed is worst-
27732804
case
@@ -2832,35 +2863,15 @@ long_from_non_binary_base(const char *start, const char *end, Py_ssize_t digits,
28322863
PyLongObject *z;
28332864
const char *p;
28342865

2835-
static double log_base_BASE[37] = {0.0e0,};
2836-
static int convwidth_base[37] = {0,};
2837-
static twodigits convmultmax_base[37] = {0,};
2838-
2839-
if (log_base_BASE[base] == 0.0) {
2840-
twodigits convmax = base;
2841-
int i = 1;
2842-
2843-
log_base_BASE[base] = (log((double)base) /
2844-
log((double)PyLong_BASE));
2845-
for (;;) {
2846-
twodigits next = convmax * base;
2847-
if (next > PyLong_BASE) {
2848-
break;
2849-
}
2850-
convmax = next;
2851-
++i;
2852-
}
2853-
convmultmax_base[base] = convmax;
2854-
assert(i > 0);
2855-
convwidth_base[base] = i;
2856-
}
2866+
/* Initialize state[base] */
2867+
_PyOnceFlag_CallOnce(&state[base].once, init_non_binary_base, &base);
28572868

28582869
/* Create an int object that can contain the largest possible
28592870
* integer with this base and length. Note that there's no
28602871
* need to initialize z->long_value.ob_digit -- no slot is read up before
28612872
* being stored into.
28622873
*/
2863-
double fsize_z = (double)digits * log_base_BASE[base] + 1.0;
2874+
double fsize_z = (double)digits * state[base].log_base_BASE + 1.0;
28642875
if (fsize_z > (double)MAX_LONG_DIGITS) {
28652876
/* The same exception as in long_alloc(). */
28662877
PyErr_SetString(PyExc_OverflowError,
@@ -2882,8 +2893,8 @@ long_from_non_binary_base(const char *start, const char *end, Py_ssize_t digits,
28822893
/* `convwidth` consecutive input digits are treated as a single
28832894
* digit in base `convmultmax`.
28842895
*/
2885-
convwidth = convwidth_base[base];
2886-
convmultmax = convmultmax_base[base];
2896+
convwidth = state[base].convwidth_base;
2897+
convmultmax = state[base].convmultmax_base;
28872898

28882899
/* Work ;-) */
28892900
p = start;

Tools/c-analyzer/cpython/ignored.tsv

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,9 @@ Python/thread_pthread.h PyThread__init_thread lib_initialized -
2929
## other values (not Python-specific)
3030

3131
## cached computed data - set lazily (*after* first init)
32+
# Thread-safe lazy init:
33+
Objects/longobject.c - state -
3234
# XXX Are these safe relative to write races?
33-
Objects/longobject.c long_from_non_binary_base log_base_BASE -
34-
Objects/longobject.c long_from_non_binary_base convwidth_base -
35-
Objects/longobject.c long_from_non_binary_base convmultmax_base -
3635
Objects/unicodeobject.c - bloom_linebreak -
3736
# This is safe:
3837
Objects/unicodeobject.c _init_global_state initialized -

0 commit comments

Comments
 (0)