Replace static bufs with a StringInfo in cash_words()
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Tue, 30 Jul 2024 19:02:58 +0000 (22:02 +0300)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Tue, 30 Jul 2024 19:02:58 +0000 (22:02 +0300)
For clarity. The code was correct, and the buffer was large enough,
but string manipulation with no bounds checking is scary.

This incurs an extra palloc+pfree to every call, but in quick
performance testing, it doesn't seem to be significant.

Reviewed-by: Robert Haas
Discussion: https://www.postgresql.org/message-id/7f86e06a-98c5-4ce3-8ec9-3885c8de0358@iki.fi

src/backend/utils/adt/cash.c

index b20c358486dc25af1216191cdc52fa4fae953d45..ec3c08acfc2df970df90b15265ee57965177e131 100644 (file)
  * Private routines
  ************************************************************************/
 
-static const char *
-num_word(Cash value)
+static void
+append_num_word(StringInfo buf, Cash value)
 {
-       static char buf[128];
        static const char *const small[] = {
                "zero", "one", "two", "three", "four", "five", "six", "seven",
                "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
@@ -50,13 +49,16 @@ num_word(Cash value)
 
        /* deal with the simple cases first */
        if (value <= 20)
-               return small[value];
+       {
+               appendStringInfoString(buf, small[value]);
+               return;
+       }
 
        /* is it an even multiple of 100? */
        if (!tu)
        {
-               sprintf(buf, "%s hundred", small[value / 100]);
-               return buf;
+               appendStringInfo(buf, "%s hundred", small[value / 100]);
+               return;
        }
 
        /* more than 99? */
@@ -64,27 +66,25 @@ num_word(Cash value)
        {
                /* is it an even multiple of 10 other than 10? */
                if (value % 10 == 0 && tu > 10)
-                       sprintf(buf, "%s hundred %s",
-                                       small[value / 100], big[tu / 10]);
+                       appendStringInfo(buf, "%s hundred %s",
+                                                        small[value / 100], big[tu / 10]);
                else if (tu < 20)
-                       sprintf(buf, "%s hundred and %s",
-                                       small[value / 100], small[tu]);
+                       appendStringInfo(buf, "%s hundred and %s",
+                                                        small[value / 100], small[tu]);
                else
-                       sprintf(buf, "%s hundred %s %s",
-                                       small[value / 100], big[tu / 10], small[tu % 10]);
+                       appendStringInfo(buf, "%s hundred %s %s",
+                                                        small[value / 100], big[tu / 10], small[tu % 10]);
        }
        else
        {
                /* is it an even multiple of 10 other than 10? */
                if (value % 10 == 0 && tu > 10)
-                       sprintf(buf, "%s", big[tu / 10]);
+                       appendStringInfoString(buf, big[tu / 10]);
                else if (tu < 20)
-                       sprintf(buf, "%s", small[tu]);
+                       appendStringInfoString(buf, small[tu]);
                else
-                       sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);
+                       appendStringInfo(buf, "%s %s", big[tu / 10], small[tu % 10]);
        }
-
-       return buf;
 }                                                              /* num_word() */
 
 static inline Cash
@@ -960,8 +960,9 @@ cash_words(PG_FUNCTION_ARGS)
 {
        Cash            value = PG_GETARG_CASH(0);
        uint64          val;
-       char            buf[256];
-       char       *p = buf;
+       StringInfoData buf;
+       text       *res;
+       Cash            dollars;
        Cash            m0;
        Cash            m1;
        Cash            m2;
@@ -970,19 +971,19 @@ cash_words(PG_FUNCTION_ARGS)
        Cash            m5;
        Cash            m6;
 
+       initStringInfo(&buf);
+
        /* work with positive numbers */
        if (value < 0)
        {
                value = -value;
-               strcpy(buf, "minus ");
-               p += 6;
+               appendStringInfoString(&buf, "minus ");
        }
-       else
-               buf[0] = '\0';
 
        /* Now treat as unsigned, to avoid trouble at INT_MIN */
        val = (uint64) value;
 
+       dollars = val / INT64CONST(100);
        m0 = val % INT64CONST(100); /* cents */
        m1 = (val / INT64CONST(100)) % 1000;    /* hundreds */
        m2 = (val / INT64CONST(100000)) % 1000; /* thousands */
@@ -993,49 +994,51 @@ cash_words(PG_FUNCTION_ARGS)
 
        if (m6)
        {
-               strcat(buf, num_word(m6));
-               strcat(buf, " quadrillion ");
+               append_num_word(&buf, m6);
+               appendStringInfoString(&buf, " quadrillion ");
        }
 
        if (m5)
        {
-               strcat(buf, num_word(m5));
-               strcat(buf, " trillion ");
+               append_num_word(&buf, m5);
+               appendStringInfoString(&buf, " trillion ");
        }
 
        if (m4)
        {
-               strcat(buf, num_word(m4));
-               strcat(buf, " billion ");
+               append_num_word(&buf, m4);
+               appendStringInfoString(&buf, " billion ");
        }
 
        if (m3)
        {
-               strcat(buf, num_word(m3));
-               strcat(buf, " million ");
+               append_num_word(&buf, m3);
+               appendStringInfoString(&buf, " million ");
        }
 
        if (m2)
        {
-               strcat(buf, num_word(m2));
-               strcat(buf, " thousand ");
+               append_num_word(&buf, m2);
+               appendStringInfoString(&buf, " thousand ");
        }
 
        if (m1)
-               strcat(buf, num_word(m1));
+               append_num_word(&buf, m1);
 
-       if (!*p)
-               strcat(buf, "zero");
+       if (dollars == 0)
+               appendStringInfoString(&buf, "zero");
 
-       strcat(buf, (val / 100) == 1 ? " dollar and " : " dollars and ");
-       strcat(buf, num_word(m0));
-       strcat(buf, m0 == 1 ? " cent" : " cents");
+       appendStringInfoString(&buf, dollars == 1 ? " dollar and " : " dollars and ");
+       append_num_word(&buf, m0);
+       appendStringInfoString(&buf, m0 == 1 ? " cent" : " cents");
 
        /* capitalize output */
-       buf[0] = pg_toupper((unsigned char) buf[0]);
+       buf.data[0] = pg_toupper((unsigned char) buf.data[0]);
 
        /* return as text datum */
-       PG_RETURN_TEXT_P(cstring_to_text(buf));
+       res = cstring_to_text_with_len(buf.data, buf.len);
+       pfree(buf.data);
+       PG_RETURN_TEXT_P(res);
 }