This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
locale.c: Differently avoid infinite recursion
authorKarl Williamson <khw@cpan.org>
Sun, 5 Nov 2023 00:57:30 +0000 (18:57 -0600)
committerKarl Williamson <khw@cpan.org>
Mon, 13 Nov 2023 00:44:45 +0000 (17:44 -0700)
This commit changes the mechanism for avoiding potential infinite
recursion in my_localeconv().

Normally the UTF8ness of the locale is determined.  Then all strings
returned by localeconv() are examined to see if their SVs need to be
marked as UTF-8 or not.  Knowing that the locale is or isn't UTF-8 helps
in that determination.

But in figuring that out, some Configurations call this function asking
for just a single item.  That would lead to infinite recursion.

To avoid that, on such Configurations prior to this commit, the
UTF8ness of the overall locale wasn't calculated, but instead each
item's UTF8ness was calculated individually.  It's complicated, but it
turns out doing this finesses the issue.  See below for a fuller
explanaation.

This commit changes things so that for single item calls, the UTF8-ness
isn't determined here, but the caller does it itself, and it doesn't
generally need the locale's UTF8ness to make that determination.

To expand on why it's complicated:  This situation arises only on
Configurations where calculating the UTF8ness of the locale may not be
reliable.  But it very likely is reliable except for English locales
whose currency symbol is plain ASCII, such as the USA and Canada and
other former members of the British Empire who use the dollar sign for
their currency symbol.  (I told you it was complicated.)  But for such
locales, the strings are going to all be ASCII, so they aren't going to
be UTF-8, so we don't need to know the locale's UTF8ness.  What both the
previous mechanism and this new one share is both use the function
get_locale_string_utf8ness_i(), and that function has the intelligence
to not need the locale's UTF8ness for an ASCII string.

locale.c

index d58e105..f2006dc 100644 (file)
--- a/locale.c
+++ b/locale.c
@@ -5131,6 +5131,21 @@ S_my_localeconv(pTHX_ const int item)
      * corrections determined at hash population time, at an extra maintenance
      * cost which khw doesn't think is worth it
      */
+
+#  ifndef HAS_SOME_LANGINFO
+
+    /* We are done when called with an individual item.  There are no integer
+     * items to adjust, and it's best for the caller to determine if this
+     * string item is UTF-8 or not.  This is because the locale's UTF-8ness is
+     * calculated below, and in some Configurations, that can lead to a
+     * recursive call to here, which could recurse infinitely. */
+
+    if (item != 0) {
+        return hv;
+    }
+
+#  endif
+
     for (unsigned int i = 0; i < 2; i++) {  /* Try both types of strings */
         if (! strings[i]) {     /* Skip if no strings of this type */
             continue;
@@ -5140,25 +5155,10 @@ S_my_localeconv(pTHX_ const int item)
                  ? numeric_locale
                  : monetary_locale;
 
-        locale_utf8ness_t locale_is_utf8 = LOCALE_UTF8NESS_UNKNOWN;
-
-#  ifdef HAS_RELIABLE_UTF8NESS_DETERMINATION
-
-        /* It saves time in the loop below to have predetermined the UTF8ness
-         * of the locale.  But only do so if the platform reliably has this
-         * information; otherwise it's better to do it only it should become
-         * necessary, which happens on a per-element basis in the loop. */
-
-        locale_is_utf8 = (is_locale_utf8(locale))
-                         ? LOCALE_IS_UTF8
-                         : LOCALE_NOT_UTF8;
-
-        if (locale_is_utf8 == LOCALE_NOT_UTF8) {
+        if (! is_locale_utf8(locale)) {
             continue;   /* No string can be UTF-8 if the locale isn't */
         }
 
-#  endif
-
         /* Examine each string */
         for (const lconv_offset_t *strp = strings[i]; strp->name; strp++) {
             const char * name = strp->name;
@@ -5172,7 +5172,7 @@ S_my_localeconv(pTHX_ const int item)
 
             /* Determine if the string should be marked as UTF-8. */
             if (UTF8NESS_YES == (get_locale_string_utf8ness_i(SvPVX(*value),
-                                                  locale_is_utf8,
+                                                  LOCALE_IS_UTF8,
                                                   NULL,
                                                   (locale_category_index) 0)))
             {
@@ -6042,12 +6042,10 @@ S_my_langinfo_i(pTHX_
         retval = save_to_buffer(SvPV_nolen(string), retbufp, retbuf_sizep);
 
         if (utf8ness) {
-            is_utf8 = (SvUTF8(string))
-                      ? UTF8NESS_YES
-                      : (is_utf8_invariant_string( (U8 *) retval,
-                                                  strlen(retval)))
-                        ? UTF8NESS_IMMATERIAL
-                        : UTF8NESS_NO;
+            is_utf8 = get_locale_string_utf8ness_i(retval,
+                                                   LOCALE_UTF8NESS_UNKNOWN,
+                                                   locale,
+                                                   cat_index);
         }
 
         break;