This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Add Perl_setlocale()
[perl5.git] / locale.c
index f163976..357f9d4 100644 (file)
--- a/locale.c
+++ b/locale.c
@@ -56,6 +56,12 @@ static bool debug_initialization = FALSE;
 #  define DEBUG_INITIALIZATION_set(v) (debug_initialization = v)
 #endif
 
+
+/* Returns the Unix errno portion; ignoring any others.  This is a macro here
+ * instead of putting it into perl.h, because unclear to khw what should be
+ * done generally. */
+#define GET_ERRNO   saved_errno
+
 /* strlen() of a literal string constant.  We might want this more general,
  * but using it in just this file for now.  A problem with more generality is
  * the compiler warnings about comparing unlike signs */
@@ -604,8 +610,8 @@ S_new_ctype(pTHX_ const char *newctype)
      * that tofold() is tolc() since fold case is not a concept in POSIX,
      *
      * Any code changing the locale (outside this file) should use
-     * POSIX::setlocale, which calls this function.  Therefore this function
-     * should be called directly only from this file and from
+     * Perl_setlocale or POSIX::setlocale, which call this function.  Therefore
+     * this function should be called directly only from this file and from
      * POSIX::setlocale() */
 
     dVAR;
@@ -823,6 +829,14 @@ S_new_ctype(pTHX_ const char *newctype)
                             );
             }
 
+#  ifdef HAS_NL_LANGINFO
+
+            Perl_sv_catpvf(aTHX_ PL_warn_locale, "; codeset=%s",
+                                    /* parameter FALSE is a don't care here */
+                                    my_nl_langinfo(PERL_CODESET, FALSE));
+
+#  endif
+
             Perl_sv_catpvf(aTHX_ PL_warn_locale, "\n");
 
             /* If we are actually in the scope of the locale or are debugging,
@@ -1153,8 +1167,12 @@ S_win32_setlocale(pTHX_ int category, const char* locale)
     }
 
     result = setlocale(category, locale);
-    DEBUG_L(PerlIO_printf(Perl_debug_log, "%s:%d: %s\n", __FILE__, __LINE__,
-                            setlocale_debug_string(category, locale, result)));
+    DEBUG_L(STMT_START {
+                dSAVE_ERRNO;
+                PerlIO_printf(Perl_debug_log, "%s:%d: %s\n", __FILE__, __LINE__,
+                            setlocale_debug_string(category, locale, result));
+                RESTORE_ERRNO;
+            } STMT_END);
 
     if (! override_LC_ALL)  {
         return result;
@@ -1177,22 +1195,56 @@ S_win32_setlocale(pTHX_ int category, const char* locale)
     }
 
     result = setlocale(LC_ALL, NULL);
-    DEBUG_L(PerlIO_printf(Perl_debug_log, "%s:%d: %s\n",
+    DEBUG_L(STMT_START {
+                dSAVE_ERRNO;
+                PerlIO_printf(Perl_debug_log, "%s:%d: %s\n",
                                __FILE__, __LINE__,
-                               setlocale_debug_string(LC_ALL, NULL, result)));
+                               setlocale_debug_string(LC_ALL, NULL, result));
+                RESTORE_ERRNO;
+            } STMT_END);
 
     return result;
 }
 
 #endif
 
-char *
-Perl_setlocale(int category, const char * locale)
+/*
+
+=head1 Locale-related functions and macros
+
+=for apidoc Perl_setlocale
+
+This is an (almost) drop-in replacement for the system L<C<setlocale(3)>>,
+taking the same parameters, and returning the same information, except that it
+returns the correct underlying C<LC_NUMERIC> locale, instead of C<C> always, as
+perl keeps that locale category as C<C>, changing it briefly during the
+operations where the underlying one is required.
+
+The other reason it isn't completely a drop-in replacement is that it is
+declared to return S<C<const char *>>, whereas the system setlocale omits the
+C<const>.  (If it were being written today, plain setlocale would be declared
+const, since it is illegal to change the information it returns; doing so leads
+to segfaults.)
+
+C<Perl_setlocale> should not be used to change the locale except on systems
+where the predefined variable C<${^SAFE_LOCALES}> is 1.
+
+The return points to a per-thread static buffer, which is overwritten the next
+time C<Perl_setlocale> is called from the same thread.
+
+=cut
+
+*/
+
+const char *
+Perl_setlocale(const int category, const char * locale)
 {
     /* This wraps POSIX::setlocale() */
 
-    char * retval;
-    char * newlocale;
+    const char * retval;
+    const char * newlocale;
+    dSAVEDERRNO;
+    DECLARATION_FOR_LC_NUMERIC_MANIPULATION;
     dTHX;
 
 #ifdef USE_LOCALE_NUMERIC
@@ -1203,14 +1255,16 @@ Perl_setlocale(int category, const char * locale)
      * results.  All other categories don't require special handling */
     if (locale == NULL) {
         if (category == LC_NUMERIC) {
-            return savepv(PL_numeric_name);
+
+            /* We don't have to copy this return value, as it is a per-thread
+             * variable, and won't change until a future setlocale */
+            return PL_numeric_name;
         }
 
 #  ifdef LC_ALL
 
-        else if (category == LC_ALL && ! PL_numeric_underlying) {
-
-            SET_NUMERIC_UNDERLYING();
+        else if (category == LC_ALL) {
+            STORE_LC_NUMERIC_FORCE_TO_UNDERLYING();
         }
 
 #  endif
@@ -1219,26 +1273,32 @@ Perl_setlocale(int category, const char * locale)
 
 #endif
 
-    /* Save retval since subsequent setlocale() calls may overwrite it. */
-    retval = savepv(do_setlocale_r(category, locale));
+    retval = do_setlocale_r(category, locale);
+    SAVE_ERRNO;
+
+#if defined(USE_LOCALE_NUMERIC) && defined(LC_ALL)
+
+    if (locale == NULL && category == LC_ALL) {
+        RESTORE_LC_NUMERIC();
+    }
+
+#endif
 
     DEBUG_L(PerlIO_printf(Perl_debug_log,
         "%s:%d: %s\n", __FILE__, __LINE__,
             setlocale_debug_string(category, locale, retval)));
-    if (! retval) {
-        /* Should never happen that a query would return an error, but be
-         * sure and reset to C locale */
-        if (locale == 0) {
-            SET_NUMERIC_STANDARD();
-        }
 
+    RESTORE_ERRNO;
+
+    if (! retval) {
         return NULL;
     }
 
-    /* If locale == NULL, we are just querying the state, but may have switched
-     * to NUMERIC_UNDERLYING.  Switch back before returning. */
+    save_to_buffer(retval, &PL_setlocale_buf, &PL_setlocale_bufsize, 0);
+    retval = PL_setlocale_buf;
+
+    /* If locale == NULL, we are just querying the state */
     if (locale == NULL) {
-        SET_NUMERIC_STANDARD();
         return retval;
     }
 
@@ -1302,7 +1362,6 @@ Perl_setlocale(int category, const char * locale)
 
     return retval;
 
-
 }
 
 PERL_STATIC_INLINE const char *
@@ -1330,8 +1389,6 @@ S_save_to_buffer(const char * string, char **buf, Size_t *buf_size, const Size_t
 
 /*
 
-=head1 Locale-related functions and macros
-
 =for apidoc Perl_langinfo
 
 This is an (almost ยช) drop-in replacement for the system C<L<nl_langinfo(3)>>,
@@ -1369,6 +1426,12 @@ both of which are specified in C89, so should be always be available.  Later
 C<strftime()> versions have additional capabilities; C<""> is returned for
 those not available on your system.
 
+It is important to note that on such systems, this calls C<localeconv>, and so
+overwrites the static buffer returned from previous explicit calls to that
+function.  Thus, if the program doesn't use or save the information from an
+explicit C<localeconv> call (which good practice suggests should be done
+anyway), use of this function can break it.
+
 The details for those items which may differ from what this emulation returns
 and what a native C<nl_langinfo()> would return are:
 
@@ -3343,8 +3406,8 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
             /* Standard UTF-8 needs at least 4 bytes to represent the maximum
              * Unicode code point. */
 
-            DEBUG_L(PerlIO_printf(Perl_debug_log, "\tMB_CUR_MAX=%d\n",
-                                       (int) MB_CUR_MAX));
+            DEBUG_L(PerlIO_printf(Perl_debug_log, "%s: %d: MB_CUR_MAX=%d\n",
+                                       __FILE__, __LINE__, (int) MB_CUR_MAX));
             if ((unsigned) MB_CUR_MAX < STRLENs(MAX_UNICODE_UTF8)) {
                 is_utf8 = FALSE;
                 restore_switched_locale(LC_CTYPE, original_ctype_locale);
@@ -3365,7 +3428,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
             const char *codeset = my_nl_langinfo(PERL_CODESET, FALSE);
                                           /* FALSE => already in dest locale */
 
-            DEBUG_L(PerlIO_printf(Perl_debug_log,
+            DEBUG_Lv(PerlIO_printf(Perl_debug_log,
                             "\tnllanginfo returned CODESET '%s'\n", codeset));
 
             if (codeset && strNE(codeset, "")) {
@@ -3396,6 +3459,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
         {
             wchar_t wc;
             int len;
+            dSAVEDERRNO;
 
 #      if defined(HAS_MBRTOWC) && defined(USE_ITHREADS)
 
@@ -3413,20 +3477,25 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
             memset(&ps, 0, sizeof(ps));;
             PERL_UNUSED_RESULT(mbrtowc(&wc, NULL, 0, &ps)); /* Reset any shift
                                                                state */
-            errno = 0;
+            SETERRNO(0, 0);
             len = mbrtowc(&wc, STR_WITH_LEN(REPLACEMENT_CHARACTER_UTF8), &ps);
+            SAVE_ERRNO;
 
 #      else
 
+            LOCALE_LOCK;
             PERL_UNUSED_RESULT(mbtowc(&wc, NULL, 0));/* Reset any shift state */
-            errno = 0;
+            SETERRNO(0, 0);
             len = mbtowc(&wc, STR_WITH_LEN(REPLACEMENT_CHARACTER_UTF8));
+            SAVE_ERRNO;
+            LOCALE_UNLOCK;
 
 #      endif
 
-            DEBUG_L(PerlIO_printf(Perl_debug_log,
+            RESTORE_ERRNO;
+            DEBUG_Lv(PerlIO_printf(Perl_debug_log,
                     "\treturn from mbtowc; len=%d; code_point=%x; errno=%d\n",
-                                   len,      (unsigned int) wc, errno));
+                                   len,      (unsigned int) wc, GET_ERRNO));
 
             is_utf8 = cBOOL(   len == STRLENs(REPLACEMENT_CHARACTER_UTF8)
                             && wc == (wchar_t) UNICODE_REPLACEMENT);
@@ -3485,7 +3554,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
                 /* It isn't a UTF-8 locale if the symbol is not legal UTF-8;
                  * otherwise assume the locale is UTF-8 if and only if the symbol
                  * is non-ascii UTF-8. */
-                DEBUG_L(PerlIO_printf(Perl_debug_log, "\t?Currency symbol for %s is UTF-8=%d\n",
+                DEBUG_Lv(PerlIO_printf(Perl_debug_log, "\t?Currency symbol for %s is UTF-8=%d\n",
                                         save_input_locale, is_utf8));
                 goto finish_and_return;
             }
@@ -3539,7 +3608,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
                  * locale if we changed it */
                 restore_switched_locale(LC_TIME, original_time_locale);
 
-                DEBUG_L(PerlIO_printf(Perl_debug_log, "\t?time-related strings for %s are UTF-8=%d\n",
+                DEBUG_Lv(PerlIO_printf(Perl_debug_log, "\t?time-related strings for %s are UTF-8=%d\n",
                                     save_input_locale,
                                     is_utf8_string((U8 *) formatted_time, 0)));
                 is_utf8 = is_utf8_string((U8 *) formatted_time, 0);
@@ -3550,7 +3619,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
              * ASCII.  Go on to the next test.  If we changed it, restore LC_TIME
              * to its original locale */
             restore_switched_locale(LC_TIME, original_time_locale);
-            DEBUG_L(PerlIO_printf(Perl_debug_log, "All time-related words for %s contain only ASCII; can't use for determining if UTF-8 locale\n", save_input_locale));
+            DEBUG_Lv(PerlIO_printf(Perl_debug_log, "All time-related words for %s contain only ASCII; can't use for determining if UTF-8 locale\n", save_input_locale));
         }
 
 #    endif
@@ -3603,7 +3672,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
 
                 /* Any non-UTF-8 message means not a UTF-8 locale; if all are valid,
                  * any non-ascii means it is one; otherwise we assume it isn't */
-                DEBUG_L(PerlIO_printf(Perl_debug_log, "\t?error messages for %s are UTF-8=%d\n",
+                DEBUG_Lv(PerlIO_printf(Perl_debug_log, "\t?error messages for %s are UTF-8=%d\n",
                                     save_input_locale,
                                     is_utf8));
                 goto finish_and_return;
@@ -3748,7 +3817,54 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
 
 #  ifdef DEBUGGING
 
+    if (DEBUG_Lv_TEST) {
+        const char * s = PL_locale_utf8ness;
+
+        /* Audit the structure */
+        while (s < PL_locale_utf8ness + strlen(PL_locale_utf8ness)) {
+            const char *e;
+
+            if (*s != UTF8NESS_SEP[0]) {
+                Perl_croak(aTHX_
+                           "panic: %s: %d: Corrupt utf8ness_cache: missing"
+                           " separator %.*s<-- HERE %s\n",
+                           __FILE__, __LINE__,
+                           (int) (s - PL_locale_utf8ness), PL_locale_utf8ness,
+                           s);
+            }
+            s++;
+            e = strchr(s, UTF8NESS_PREFIX[0]);
+            if (! e) {
+                Perl_croak(aTHX_
+                           "panic: %s: %d: Corrupt utf8ness_cache: missing"
+                           " separator %.*s<-- HERE %s\n",
+                           __FILE__, __LINE__,
+                           (int) (e - PL_locale_utf8ness), PL_locale_utf8ness,
+                           e);
+            }
+            e++;
+            if (*e != '0' && *e != '1') {
+                Perl_croak(aTHX_
+                           "panic: %s: %d: Corrupt utf8ness_cache: utf8ness"
+                           " must be [01] %.*s<-- HERE %s\n",
+                           __FILE__, __LINE__,
+                           (int) (e + 1 - PL_locale_utf8ness),
+                           PL_locale_utf8ness, e + 1);
+            }
+            if (ninstr(PL_locale_utf8ness, s, s-1, e)) {
+                Perl_croak(aTHX_
+                           "panic: %s: %d: Corrupt utf8ness_cache: entry"
+                           " has duplicate %.*s<-- HERE %s\n",
+                           __FILE__, __LINE__,
+                           (int) (e - PL_locale_utf8ness), PL_locale_utf8ness,
+                           e);
+            }
+            s = e + 1;
+        }
+    }
+
     if (DEBUG_Lv_TEST || debug_initialization) {
+
         PerlIO_printf(Perl_debug_log,
                 "PL_locale_utf8ness is now %s; returning %d\n",
                                      PL_locale_utf8ness, is_utf8);