This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
locale.c: Work around a Win32 bug in localeconv()
authorKarl Williamson <khw@cpan.org>
Mon, 28 Nov 2022 17:45:56 +0000 (10:45 -0700)
committerKarl Williamson <khw@cpan.org>
Mon, 10 Jul 2023 13:37:09 +0000 (07:37 -0600)
If the locale for an individual category is set, and then the locale for
LC_CTYPE is set afterwards, it trashes (at least partially), the first
category's setting for the purposes of localeconv().  This can happen
even if LC_CTYPE is the same locale as that of the other category.

This commit implements a workaround for that, where LC_CTYPE is set,
then the other cateorgy's locale is switched to C, then to whatever is
should be.   The problem doesn't seem to happen if the destination
locale is C.

I have a short C-language reproducer program that doesn't involve Perl,
but I haven't submitted it to Microsoft due to my cynicism about them
patying any attention to it.

locale.c

index 99c7918..c7dfa38 100644 (file)
--- a/locale.c
+++ b/locale.c
@@ -3682,11 +3682,44 @@ S_populate_hash_from_localeconv(pTHX_ HV * hv,
     const char * orig_NUMERIC_locale = NULL;
     if (which_mask & INDEX_TO_BIT(LC_NUMERIC_INDEX_)) {
         LC_NUMERIC_LOCK(0);
+
+#    if defined(WIN32)
+
+        /* There is a bug in Windows in which setting LC_CTYPE after the others
+         * doesn't actually take effect for localeconv().  See the commit
+         * message for this commit for details.  Thus we have to make sure that
+         * the locale we want is set after LC_CTYPE.  We unconditionally toggle
+         * away from and back to the current locale prior to calling
+         * localeconv().
+         *
+         * This code will have no effect if we already are in C, but khw
+         * hasn't seen any cases where this causes problems when we are in the
+         * C locale. */
+        orig_NUMERIC_locale = toggle_locale_i(LC_NUMERIC_INDEX_, "C");
+        toggle_locale_i(LC_NUMERIC_INDEX_, locale);
+
+#    else
+
+        /* No need for the extra toggle when not on Windows */
         orig_NUMERIC_locale = toggle_locale_i(LC_NUMERIC_INDEX_, locale);
-    }
 
 #    endif
 
+    }
+
+#  endif
+#  if defined(USE_LOCALE_MONETARY) && defined(WIN32)
+
+    /* Same Windows bug as described just above for NUMERIC.  Otherwise, no
+     * need to toggle LC_MONETARY, as it is kept in the underlying locale */
+    const char * orig_MONETARY_locale = NULL;
+    if (which_mask & INDEX_TO_BIT(LC_MONETARY_INDEX_)) {
+        orig_MONETARY_locale = toggle_locale_i(LC_MONETARY_INDEX_, "C");
+        toggle_locale_i(LC_MONETARY_INDEX_, locale);
+    }
+
+#  endif
+
     /* Finally ready to do the actual localeconv().  Lock to prevent other
      * accesses until we have made a copy of its returned static buffer */
     gwLOCALE_LOCK;
@@ -3817,6 +3850,11 @@ S_populate_hash_from_localeconv(pTHX_ HV * hv,
     gwLOCALE_UNLOCK;    /* Finished with the critical section of a
                            globally-accessible buffer */
 
+#  if defined(USE_LOCALE_MONETARY) && defined(WIN32)
+
+    restore_toggled_locale_i(LC_MONETARY_INDEX_, orig_MONETARY_locale);
+
+#  endif
 #  ifdef USE_LOCALE_NUMERIC
 
     restore_toggled_locale_i(LC_NUMERIC_INDEX_, orig_NUMERIC_locale);