Skip to content

Commit df3051b

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

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.6.5
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

@@ -171,11 +247,10 @@ PyLocale_localeconv(PyObject* self)
171247
RESULT(#i, x); \
172248
} while (0)
173249

174-
/* Monetary information */
175-
RESULT_STRING(int_curr_symbol);
176-
RESULT_STRING(currency_symbol);
177-
RESULT_STRING(mon_decimal_point);
178-
RESULT_STRING(mon_thousands_sep);
250+
/* Monetary information: LC_MONETARY encoding */
251+
if (locale_decode_monetary(result, l) < 0) {
252+
goto failed;
253+
}
179254
x = copy_grouping(l->mon_grouping);
180255
RESULT("mon_grouping", x);
181256

@@ -190,7 +265,7 @@ PyLocale_localeconv(PyObject* self)
190265
RESULT_INT(p_sign_posn);
191266
RESULT_INT(n_sign_posn);
192267

193-
/* Numeric information */
268+
/* Numeric information: LC_NUMERIC encoding */
194269
PyObject *decimal_point, *thousands_sep;
195270
const char *grouping;
196271
if (_Py_GetLocaleconvNumeric(&decimal_point,
@@ -220,6 +295,10 @@ PyLocale_localeconv(PyObject* self)
220295
failed:
221296
Py_XDECREF(result);
222297
return NULL;
298+
299+
#undef RESULT
300+
#undef RESULT_STRING
301+
#undef RESULT_INT
223302
}
224303

225304
#if defined(HAVE_WCSCOLL)

Python/fileutils.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1687,7 +1687,7 @@ _Py_GetLocaleconvNumeric(PyObject **decimal_point, PyObject **thousands_sep,
16871687
if (change_locale) {
16881688
oldloc = setlocale(LC_CTYPE, NULL);
16891689
if (!oldloc) {
1690-
PyErr_SetString(PyExc_RuntimeWarning, "faild to get LC_CTYPE locale");
1690+
PyErr_SetString(PyExc_RuntimeWarning, "failed to get LC_CTYPE locale");
16911691
return -1;
16921692
}
16931693

@@ -1703,7 +1703,7 @@ _Py_GetLocaleconvNumeric(PyObject **decimal_point, PyObject **thousands_sep,
17031703
}
17041704

17051705
if (loc != NULL) {
1706-
/* Only set the locale temporarilty the LC_CTYPE locale
1706+
/* Only set the locale temporarily the LC_CTYPE locale
17071707
if LC_NUMERIC locale is different than LC_CTYPE locale and
17081708
decimal_point and/or thousands_sep are non-ASCII or longer than
17091709
1 byte */

Python/formatter_unicode.c

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

402-
#define STATIC_LOCALE_INFO_INIT {0, 0, 0}
403+
#define STATIC_LOCALE_INFO_INIT {0, 0, 0, 0}
403404

404405
/* describes the layout for an integer, see the comment in
405406
calc_number_widths() for details */
@@ -708,11 +709,22 @@ get_locale_info(enum LocaleType type, LocaleInfo *locale_info)
708709
{
709710
switch (type) {
710711
case LT_CURRENT_LOCALE: {
712+
const char *grouping;
711713
if (_Py_GetLocaleconvNumeric(&locale_info->decimal_point,
712714
&locale_info->thousands_sep,
713-
&locale_info->grouping) < 0) {
715+
&grouping) < 0) {
714716
return -1;
715717
}
718+
719+
/* localeconv() grouping can become a dangling pointer or point
720+
to a different string if another thread calls localeconv() during
721+
the string formatting. Copy the string to avoid this risk. */
722+
locale_info->grouping_buffer = _PyMem_Strdup(grouping);
723+
if (locale_info->grouping_buffer == NULL) {
724+
PyErr_NoMemory();
725+
return -1;
726+
}
727+
locale_info->grouping = locale_info->grouping_buffer;
716728
break;
717729
}
718730
case LT_DEFAULT_LOCALE:
@@ -746,6 +758,7 @@ free_locale_info(LocaleInfo *locale_info)
746758
{
747759
Py_XDECREF(locale_info->decimal_point);
748760
Py_XDECREF(locale_info->thousands_sep);
761+
PyMem_Free(locale_info->grouping_buffer);
749762
}
750763

751764
/************************************************************************/

0 commit comments

Comments
 (0)