Skip to content

Commit 6eff6b8

Browse files
authored
bpo-28604: Fix localeconv() for different LC_MONETARY (GH-10606) (GH-10619)
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(). (cherry picked from commit 02e6bf7)
1 parent d57ab8a commit 6eff6b8

File tree

5 files changed

+107
-14
lines changed

5 files changed

+107
-14
lines changed

Doc/library/locale.rst

Lines changed: 2 additions & 4 deletions
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
Lines changed: 3 additions & 0 deletions
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.

Modules/_localemodule.c

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,82 @@ PyLocale_setlocale(PyObject* self, PyObject* args)
128128
return result_object;
129129
}
130130

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

@@ -172,11 +248,10 @@ PyLocale_localeconv(PyObject* self)
172248
RESULT(#i, x); \
173249
} while (0)
174250

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);
251+
/* Monetary information: LC_MONETARY encoding */
252+
if (locale_decode_monetary(result, l) < 0) {
253+
goto failed;
254+
}
180255
x = copy_grouping(l->mon_grouping);
181256
RESULT("mon_grouping", x);
182257

@@ -191,7 +266,7 @@ PyLocale_localeconv(PyObject* self)
191266
RESULT_INT(p_sign_posn);
192267
RESULT_INT(n_sign_posn);
193268

194-
/* Numeric information */
269+
/* Numeric information: LC_NUMERIC encoding */
195270
PyObject *decimal_point, *thousands_sep;
196271
const char *grouping;
197272
if (_Py_GetLocaleconvNumeric(&decimal_point,
@@ -221,6 +296,10 @@ PyLocale_localeconv(PyObject* self)
221296
failed:
222297
Py_DECREF(result);
223298
return NULL;
299+
300+
#undef RESULT
301+
#undef RESULT_STRING
302+
#undef RESULT_INT
224303
}
225304

226305
#if defined(HAVE_WCSCOLL)

Python/fileutils.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1801,7 +1801,7 @@ _Py_GetLocaleconvNumeric(PyObject **decimal_point, PyObject **thousands_sep,
18011801
if (change_locale) {
18021802
oldloc = setlocale(LC_CTYPE, NULL);
18031803
if (!oldloc) {
1804-
PyErr_SetString(PyExc_RuntimeWarning, "faild to get LC_CTYPE locale");
1804+
PyErr_SetString(PyExc_RuntimeWarning, "failed to get LC_CTYPE locale");
18051805
return -1;
18061806
}
18071807

@@ -1817,7 +1817,7 @@ _Py_GetLocaleconvNumeric(PyObject **decimal_point, PyObject **thousands_sep,
18171817
}
18181818

18191819
if (loc != NULL) {
1820-
/* Only set the locale temporarilty the LC_CTYPE locale
1820+
/* Only set the locale temporarily the LC_CTYPE locale
18211821
if LC_NUMERIC locale is different than LC_CTYPE locale and
18221822
decimal_point and/or thousands_sep are non-ASCII or longer than
18231823
1 byte */

Python/formatter_unicode.c

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,9 +396,10 @@ typedef struct {
396396
PyObject *decimal_point;
397397
PyObject *thousands_sep;
398398
const char *grouping;
399+
char *grouping_buffer;
399400
} LocaleInfo;
400401

401-
#define STATIC_LOCALE_INFO_INIT {0, 0, 0}
402+
#define STATIC_LOCALE_INFO_INIT {0, 0, 0, 0}
402403

403404
/* describes the layout for an integer, see the comment in
404405
calc_number_widths() for details */
@@ -705,11 +706,22 @@ get_locale_info(enum LocaleType type, LocaleInfo *locale_info)
705706
{
706707
switch (type) {
707708
case LT_CURRENT_LOCALE: {
709+
const char *grouping;
708710
if (_Py_GetLocaleconvNumeric(&locale_info->decimal_point,
709711
&locale_info->thousands_sep,
710-
&locale_info->grouping) < 0) {
712+
&grouping) < 0) {
711713
return -1;
712714
}
715+
716+
/* localeconv() grouping can become a dangling pointer or point
717+
to a different string if another thread calls localeconv() during
718+
the string formatting. Copy the string to avoid this risk. */
719+
locale_info->grouping_buffer = _PyMem_Strdup(grouping);
720+
if (locale_info->grouping_buffer == NULL) {
721+
PyErr_NoMemory();
722+
return -1;
723+
}
724+
locale_info->grouping = locale_info->grouping_buffer;
713725
break;
714726
}
715727
case LT_DEFAULT_LOCALE:
@@ -743,6 +755,7 @@ free_locale_info(LocaleInfo *locale_info)
743755
{
744756
Py_XDECREF(locale_info->decimal_point);
745757
Py_XDECREF(locale_info->thousands_sep);
758+
PyMem_Free(locale_info->grouping_buffer);
746759
}
747760

748761
/************************************************************************/

0 commit comments

Comments
 (0)