Skip to content

Commit 02e6bf7

Browse files
authored
bpo-28604: Fix localeconv() for different LC_MONETARY (GH-10606)
locale.localeconv() now sets temporarily the LC_CTYPE locale to the LC_MONETARY locale if the two locales are different and monetary strings are non-ASCII. This temporary change affects other threads. Changes: * locale.localeconv() can now set LC_CTYPE to LC_MONETARY to decode monetary fields. * Add LocaleInfo.grouping_buffer: copy localeconv() grouping string since it can be replaced anytime if a different thread calls localeconv(). * _Py_GetLocaleconvNumeric() now requires a "struct lconv *" structure, so locale.localeconv() now longer calls localeconv() twice. Moreover, the function now requires all arguments to be non-NULL. * Rename STATIC_LOCALE_INFO_INIT to LocaleInfo_STATIC_INIT. * Move _Py_GetLocaleconvNumeric() definition from fileutils.h to pycore_fileutils.h. pycore_fileutils.h now includes locale.h. * The _locale module is now built with Py_BUILD_CORE defined.
1 parent d5d3368 commit 02e6bf7

File tree

8 files changed

+145
-60
lines changed

8 files changed

+145
-60
lines changed

Diff for: Doc/library/locale.rst

+2-4
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,8 @@ The :mod:`locale` module defines the following exception and functions:
148148
+--------------+-----------------------------------------+
149149

150150
The function sets temporarily the ``LC_CTYPE`` locale to the ``LC_NUMERIC``
151-
locale to decode ``decimal_point`` and ``thousands_sep`` byte strings if
152-
they are non-ASCII or longer than 1 byte, and the ``LC_NUMERIC`` locale is
153-
different than the ``LC_CTYPE`` locale. This temporary change affects other
154-
threads.
151+
locale or the ``LC_MONETARY`` locale if locales are different and numeric or
152+
monetary strings are non-ASCII. This temporary change affects other threads.
155153

156154
.. versionchanged:: 3.7
157155
The function now sets temporarily the ``LC_CTYPE`` locale to the

Diff for: Include/fileutils.h

-5
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,6 @@ PyAPI_FUNC(int) _Py_get_blocking(int fd);
170170
PyAPI_FUNC(int) _Py_set_blocking(int fd, int blocking);
171171
#endif /* !MS_WINDOWS */
172172

173-
PyAPI_FUNC(int) _Py_GetLocaleconvNumeric(
174-
PyObject **decimal_point,
175-
PyObject **thousands_sep,
176-
const char **grouping);
177-
178173
#endif /* Py_LIMITED_API */
179174

180175
#ifdef __cplusplus

Diff for: Include/internal/pycore_fileutils.h

+7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ extern "C" {
88
# error "Py_BUILD_CORE must be defined to include this header"
99
#endif
1010

11+
#include <locale.h> /* struct lconv */
12+
1113
PyAPI_FUNC(int) _Py_DecodeUTF8Ex(
1214
const char *arg,
1315
Py_ssize_t arglen,
@@ -30,6 +32,11 @@ PyAPI_FUNC(wchar_t*) _Py_DecodeUTF8_surrogateescape(
3032

3133
PyAPI_FUNC(int) _Py_GetForceASCII(void);
3234

35+
PyAPI_FUNC(int) _Py_GetLocaleconvNumeric(
36+
struct lconv *lc,
37+
PyObject **decimal_point,
38+
PyObject **thousands_sep);
39+
3340
#ifdef __cplusplus
3441
}
3542
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:func:`locale.localeconv` now sets temporarily the ``LC_CTYPE`` locale to the
2+
``LC_MONETARY`` locale if the two locales are different and monetary strings
3+
are non-ASCII. This temporary change affects other threads.

Diff for: Modules/Setup

+1-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ time -DPy_BUILD_CORE -I$(srcdir)/Include/internal timemodule.c # -lm # time oper
120120
_thread -DPy_BUILD_CORE -I$(srcdir)/Include/internal _threadmodule.c # low-level threading interface
121121

122122
# access to ISO C locale support
123-
_locale _localemodule.c # -lintl
123+
_locale -DPy_BUILD_CORE _localemodule.c # -lintl
124124

125125
# Standard I/O baseline
126126
_io -DPy_BUILD_CORE -I$(srcdir)/Include/internal -I$(srcdir)/Modules/_io _io/_iomodule.c _io/iobase.c _io/fileio.c _io/bytesio.c _io/bufferedio.c _io/textio.c _io/stringio.c

Diff for: Modules/_localemodule.c

+93-16
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This software comes with no warranty. Use at your own risk.
1111

1212
#define PY_SSIZE_T_CLEAN
1313
#include "Python.h"
14+
#include "pycore_fileutils.h"
1415

1516
#include <stdio.h>
1617
#include <locale.h>
@@ -128,14 +129,90 @@ PyLocale_setlocale(PyObject* self, PyObject* args)
128129
return result_object;
129130
}
130131

132+
static int
133+
locale_is_ascii(const char *str)
134+
{
135+
return (strlen(str) == 1 && ((unsigned char)str[0]) <= 127);
136+
}
137+
138+
static int
139+
locale_decode_monetary(PyObject *dict, struct lconv *lc)
140+
{
141+
int change_locale;
142+
change_locale = (!locale_is_ascii(lc->int_curr_symbol)
143+
|| !locale_is_ascii(lc->currency_symbol)
144+
|| !locale_is_ascii(lc->mon_decimal_point)
145+
|| !locale_is_ascii(lc->mon_thousands_sep));
146+
147+
/* Keep a copy of the LC_CTYPE locale */
148+
char *oldloc = NULL, *loc = NULL;
149+
if (change_locale) {
150+
oldloc = setlocale(LC_CTYPE, NULL);
151+
if (!oldloc) {
152+
PyErr_SetString(PyExc_RuntimeWarning,
153+
"failed to get LC_CTYPE locale");
154+
return -1;
155+
}
156+
157+
oldloc = _PyMem_Strdup(oldloc);
158+
if (!oldloc) {
159+
PyErr_NoMemory();
160+
return -1;
161+
}
162+
163+
loc = setlocale(LC_MONETARY, NULL);
164+
if (loc != NULL && strcmp(loc, oldloc) == 0) {
165+
loc = NULL;
166+
}
167+
168+
if (loc != NULL) {
169+
/* Only set the locale temporarily the LC_CTYPE locale
170+
to the LC_MONETARY locale if the two locales are different and
171+
at least one string is non-ASCII. */
172+
setlocale(LC_CTYPE, loc);
173+
}
174+
}
175+
176+
int res = -1;
177+
178+
#define RESULT_STRING(ATTR) \
179+
do { \
180+
PyObject *obj; \
181+
obj = PyUnicode_DecodeLocale(lc->ATTR, NULL); \
182+
if (obj == NULL) { \
183+
goto done; \
184+
} \
185+
if (PyDict_SetItemString(dict, Py_STRINGIFY(ATTR), obj) < 0) { \
186+
Py_DECREF(obj); \
187+
goto done; \
188+
} \
189+
Py_DECREF(obj); \
190+
} while (0)
191+
192+
RESULT_STRING(int_curr_symbol);
193+
RESULT_STRING(currency_symbol);
194+
RESULT_STRING(mon_decimal_point);
195+
RESULT_STRING(mon_thousands_sep);
196+
#undef RESULT_STRING
197+
198+
res = 0;
199+
200+
done:
201+
if (loc != NULL) {
202+
setlocale(LC_CTYPE, oldloc);
203+
}
204+
PyMem_Free(oldloc);
205+
return res;
206+
}
207+
131208
PyDoc_STRVAR(localeconv__doc__,
132209
"() -> dict. Returns numeric and monetary locale-specific parameters.");
133210

134211
static PyObject*
135212
PyLocale_localeconv(PyObject* self, PyObject *Py_UNUSED(ignored))
136213
{
137214
PyObject* result;
138-
struct lconv *l;
215+
struct lconv *lc;
139216
PyObject *x;
140217

141218
result = PyDict_New();
@@ -144,7 +221,7 @@ PyLocale_localeconv(PyObject* self, PyObject *Py_UNUSED(ignored))
144221
}
145222

146223
/* if LC_NUMERIC is different in the C library, use saved value */
147-
l = localeconv();
224+
lc = localeconv();
148225

149226
/* hopefully, the localeconv result survives the C library calls
150227
involved herein */
@@ -162,22 +239,21 @@ PyLocale_localeconv(PyObject* self, PyObject *Py_UNUSED(ignored))
162239

163240
#define RESULT_STRING(s)\
164241
do { \
165-
x = PyUnicode_DecodeLocale(l->s, NULL); \
242+
x = PyUnicode_DecodeLocale(lc->s, NULL); \
166243
RESULT(#s, x); \
167244
} while (0)
168245

169246
#define RESULT_INT(i)\
170247
do { \
171-
x = PyLong_FromLong(l->i); \
248+
x = PyLong_FromLong(lc->i); \
172249
RESULT(#i, x); \
173250
} while (0)
174251

175-
/* Monetary information */
176-
RESULT_STRING(int_curr_symbol);
177-
RESULT_STRING(currency_symbol);
178-
RESULT_STRING(mon_decimal_point);
179-
RESULT_STRING(mon_thousands_sep);
180-
x = copy_grouping(l->mon_grouping);
252+
/* Monetary information: LC_MONETARY encoding */
253+
if (locale_decode_monetary(result, lc) < 0) {
254+
goto failed;
255+
}
256+
x = copy_grouping(lc->mon_grouping);
181257
RESULT("mon_grouping", x);
182258

183259
RESULT_STRING(positive_sign);
@@ -191,12 +267,9 @@ PyLocale_localeconv(PyObject* self, PyObject *Py_UNUSED(ignored))
191267
RESULT_INT(p_sign_posn);
192268
RESULT_INT(n_sign_posn);
193269

194-
/* Numeric information */
270+
/* Numeric information: LC_NUMERIC encoding */
195271
PyObject *decimal_point, *thousands_sep;
196-
const char *grouping;
197-
if (_Py_GetLocaleconvNumeric(&decimal_point,
198-
&thousands_sep,
199-
&grouping) < 0) {
272+
if (_Py_GetLocaleconvNumeric(lc, &decimal_point, &thousands_sep) < 0) {
200273
goto failed;
201274
}
202275

@@ -213,14 +286,18 @@ PyLocale_localeconv(PyObject* self, PyObject *Py_UNUSED(ignored))
213286
}
214287
Py_DECREF(thousands_sep);
215288

216-
x = copy_grouping(grouping);
289+
x = copy_grouping(lc->grouping);
217290
RESULT("grouping", x);
218291

219292
return result;
220293

221294
failed:
222295
Py_DECREF(result);
223296
return NULL;
297+
298+
#undef RESULT
299+
#undef RESULT_STRING
300+
#undef RESULT_INT
224301
}
225302

226303
#if defined(HAVE_WCSCOLL)

Diff for: Python/fileutils.c

+18-27
Original file line numberDiff line numberDiff line change
@@ -1868,22 +1868,17 @@ _Py_set_blocking(int fd, int blocking)
18681868

18691869

18701870
int
1871-
_Py_GetLocaleconvNumeric(PyObject **decimal_point, PyObject **thousands_sep,
1872-
const char **grouping)
1871+
_Py_GetLocaleconvNumeric(struct lconv *lc,
1872+
PyObject **decimal_point, PyObject **thousands_sep)
18731873
{
1874-
int res = -1;
1875-
1876-
struct lconv *lc = localeconv();
1874+
assert(decimal_point != NULL);
1875+
assert(thousands_sep != NULL);
18771876

18781877
int change_locale = 0;
1879-
if (decimal_point != NULL &&
1880-
(strlen(lc->decimal_point) > 1 || ((unsigned char)lc->decimal_point[0]) > 127))
1881-
{
1878+
if ((strlen(lc->decimal_point) > 1 || ((unsigned char)lc->decimal_point[0]) > 127)) {
18821879
change_locale = 1;
18831880
}
1884-
if (thousands_sep != NULL &&
1885-
(strlen(lc->thousands_sep) > 1 || ((unsigned char)lc->thousands_sep[0]) > 127))
1886-
{
1881+
if ((strlen(lc->thousands_sep) > 1 || ((unsigned char)lc->thousands_sep[0]) > 127)) {
18871882
change_locale = 1;
18881883
}
18891884

@@ -1892,7 +1887,8 @@ _Py_GetLocaleconvNumeric(PyObject **decimal_point, PyObject **thousands_sep,
18921887
if (change_locale) {
18931888
oldloc = setlocale(LC_CTYPE, NULL);
18941889
if (!oldloc) {
1895-
PyErr_SetString(PyExc_RuntimeWarning, "faild to get LC_CTYPE locale");
1890+
PyErr_SetString(PyExc_RuntimeWarning,
1891+
"failed to get LC_CTYPE locale");
18961892
return -1;
18971893
}
18981894

@@ -1908,34 +1904,29 @@ _Py_GetLocaleconvNumeric(PyObject **decimal_point, PyObject **thousands_sep,
19081904
}
19091905

19101906
if (loc != NULL) {
1911-
/* Only set the locale temporarilty the LC_CTYPE locale
1907+
/* Only set the locale temporarily the LC_CTYPE locale
19121908
if LC_NUMERIC locale is different than LC_CTYPE locale and
19131909
decimal_point and/or thousands_sep are non-ASCII or longer than
19141910
1 byte */
19151911
setlocale(LC_CTYPE, loc);
19161912
}
19171913
}
19181914

1919-
if (decimal_point != NULL) {
1920-
*decimal_point = PyUnicode_DecodeLocale(lc->decimal_point, NULL);
1921-
if (*decimal_point == NULL) {
1922-
goto error;
1923-
}
1924-
}
1925-
if (thousands_sep != NULL) {
1926-
*thousands_sep = PyUnicode_DecodeLocale(lc->thousands_sep, NULL);
1927-
if (*thousands_sep == NULL) {
1928-
goto error;
1929-
}
1915+
int res = -1;
1916+
1917+
*decimal_point = PyUnicode_DecodeLocale(lc->decimal_point, NULL);
1918+
if (*decimal_point == NULL) {
1919+
goto done;
19301920
}
19311921

1932-
if (grouping != NULL) {
1933-
*grouping = lc->grouping;
1922+
*thousands_sep = PyUnicode_DecodeLocale(lc->thousands_sep, NULL);
1923+
if (*thousands_sep == NULL) {
1924+
goto done;
19341925
}
19351926

19361927
res = 0;
19371928

1938-
error:
1929+
done:
19391930
if (loc != NULL) {
19401931
setlocale(LC_CTYPE, oldloc);
19411932
}

Diff for: Python/formatter_unicode.c

+21-7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
of int.__float__, etc., that take and return unicode objects */
44

55
#include "Python.h"
6+
#include "pycore_fileutils.h"
67
#include <locale.h>
78

89
/* Raises an exception about an unknown presentation type for this
@@ -396,9 +397,10 @@ typedef struct {
396397
PyObject *decimal_point;
397398
PyObject *thousands_sep;
398399
const char *grouping;
400+
char *grouping_buffer;
399401
} LocaleInfo;
400402

401-
#define STATIC_LOCALE_INFO_INIT {0, 0, 0}
403+
#define LocaleInfo_STATIC_INIT {0, 0, 0, 0}
402404

403405
/* describes the layout for an integer, see the comment in
404406
calc_number_widths() for details */
@@ -705,11 +707,22 @@ get_locale_info(enum LocaleType type, LocaleInfo *locale_info)
705707
{
706708
switch (type) {
707709
case LT_CURRENT_LOCALE: {
708-
if (_Py_GetLocaleconvNumeric(&locale_info->decimal_point,
709-
&locale_info->thousands_sep,
710-
&locale_info->grouping) < 0) {
710+
struct lconv *lc = localeconv();
711+
if (_Py_GetLocaleconvNumeric(lc,
712+
&locale_info->decimal_point,
713+
&locale_info->thousands_sep) < 0) {
711714
return -1;
712715
}
716+
717+
/* localeconv() grouping can become a dangling pointer or point
718+
to a different string if another thread calls localeconv() during
719+
the string formatting. Copy the string to avoid this risk. */
720+
locale_info->grouping_buffer = _PyMem_Strdup(lc->grouping);
721+
if (locale_info->grouping_buffer == NULL) {
722+
PyErr_NoMemory();
723+
return -1;
724+
}
725+
locale_info->grouping = locale_info->grouping_buffer;
713726
break;
714727
}
715728
case LT_DEFAULT_LOCALE:
@@ -743,6 +756,7 @@ free_locale_info(LocaleInfo *locale_info)
743756
{
744757
Py_XDECREF(locale_info->decimal_point);
745758
Py_XDECREF(locale_info->thousands_sep);
759+
PyMem_Free(locale_info->grouping_buffer);
746760
}
747761

748762
/************************************************************************/
@@ -855,7 +869,7 @@ format_long_internal(PyObject *value, const InternalFormatSpec *format,
855869

856870
/* Locale settings, either from the actual locale or
857871
from a hard-code pseudo-locale */
858-
LocaleInfo locale = STATIC_LOCALE_INFO_INIT;
872+
LocaleInfo locale = LocaleInfo_STATIC_INIT;
859873

860874
/* no precision allowed on integers */
861875
if (format->precision != -1) {
@@ -1027,7 +1041,7 @@ format_float_internal(PyObject *value,
10271041

10281042
/* Locale settings, either from the actual locale or
10291043
from a hard-code pseudo-locale */
1030-
LocaleInfo locale = STATIC_LOCALE_INFO_INIT;
1044+
LocaleInfo locale = LocaleInfo_STATIC_INIT;
10311045

10321046
if (format->precision > INT_MAX) {
10331047
PyErr_SetString(PyExc_ValueError, "precision too big");
@@ -1190,7 +1204,7 @@ format_complex_internal(PyObject *value,
11901204

11911205
/* Locale settings, either from the actual locale or
11921206
from a hard-code pseudo-locale */
1193-
LocaleInfo locale = STATIC_LOCALE_INFO_INIT;
1207+
LocaleInfo locale = LocaleInfo_STATIC_INIT;
11941208

11951209
if (format->precision > INT_MAX) {
11961210
PyErr_SetString(PyExc_ValueError, "precision too big");

0 commit comments

Comments
 (0)