Re: Report a potential memory leak in setup_config() - Mailing list pgsql-bugs

From Tom Lane
Subject Re: Report a potential memory leak in setup_config()
Date
Msg-id 3255493.1644985807@sss.pgh.pa.us
Whole thread Raw
In response to Re: Report a potential memory leak in setup_config()  (Andres Freund <andres@anarazel.de>)
Responses Re: Report a potential memory leak in setup_config()  (wliang@stu.xidian.edu.cn)
Re: Report a potential memory leak in setup_config()  (Andres Freund <andres@anarazel.de>)
List pgsql-bugs
Andres Freund <andres@anarazel.de> writes:
> When I'd looked at this last I apparently (looking at git stash, I ended up
> discarding this) decided that the best way would be to change replace_token's
> API to one that just processes one line at a time, with an outer loop that
> processes all tokens in a line.  I'm not really sure why anymore.

Hmm, I did it the other way, as attached.  This gets it down to

==3254266== LEAK SUMMARY:
==3254266==    definitely lost: 342 bytes in 17 blocks
==3254266==    indirectly lost: 152 bytes in 2 blocks
==3254266==      possibly lost: 0 bytes in 0 blocks
==3254266==    still reachable: 2,403 bytes in 22 blocks
==3254266==         suppressed: 0 bytes in 0 blocks

It seems that actually the pointer arrays *are* a big chunk of
the leakage, because the individual strings get freed in the
output loops!  That's a bit ugly but I feel no need to change it.

            regards, tom lane

diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 97f15971e2..76cb3a142e 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -237,11 +237,11 @@ static const char *const subdirs[] = {
 static char bin_path[MAXPGPATH];
 static char backend_exec[MAXPGPATH];

-static char **replace_token(char **lines,
+static void replace_token(char **lines,
                             const char *token, const char *replacement);

 #ifndef HAVE_UNIX_SOCKETS
-static char **filter_lines_with_token(char **lines, const char *token);
+static void remove_lines_with_token(char **lines, const char *token);
 #endif
 static char **readfile(const char *path);
 static void writefile(char *path, char **lines);
@@ -363,43 +363,33 @@ escape_quotes_bki(const char *src)
 }

 /*
- * make a copy of the array of lines, with token replaced by replacement
+ * Modify an array of malloc'd strings, replacing token by replacement
  * the first time it occurs on each line.
  *
  * This does most of what sed was used for in the shell script, but
  * doesn't need any regexp stuff.
  */
-static char **
+static void
 replace_token(char **lines, const char *token, const char *replacement)
 {
-    int            numlines = 1;
     int            i;
-    char      **result;
     int            toklen,
                 replen,
                 diff;

-    for (i = 0; lines[i]; i++)
-        numlines++;
-
-    result = (char **) pg_malloc(numlines * sizeof(char *));
-
     toklen = strlen(token);
     replen = strlen(replacement);
     diff = replen - toklen;

-    for (i = 0; i < numlines; i++)
+    for (i = 0; lines[i]; i++)
     {
         char       *where;
         char       *newline;
         int            pre;

-        /* just copy pointer if NULL or no change needed */
-        if (lines[i] == NULL || (where = strstr(lines[i], token)) == NULL)
-        {
-            result[i] = lines[i];
+        /* no work if no change needed */
+        if ((where = strstr(lines[i], token)) == NULL)
             continue;
-        }

         /* if we get here a change is needed - set up new line */

@@ -413,39 +403,31 @@ replace_token(char **lines, const char *token, const char *replacement)

         strcpy(newline + pre + replen, lines[i] + pre + toklen);

-        result[i] = newline;
+        free(lines[i]);
+        lines[i] = newline;
     }
-
-    return result;
 }

 /*
- * make a copy of lines without any that contain the token
+ * remove strings from lines[] if they contain the token
  *
  * a sort of poor man's grep -v
  */
 #ifndef HAVE_UNIX_SOCKETS
-static char **
-filter_lines_with_token(char **lines, const char *token)
+static void
+remove_lines_with_token(char **lines, const char *token)
 {
-    int            numlines = 1;
-    int            i,
-                src,
+    int            src,
                 dst;
-    char      **result;
-
-    for (i = 0; lines[i]; i++)
-        numlines++;
-
-    result = (char **) pg_malloc(numlines * sizeof(char *));

-    for (src = 0, dst = 0; src < numlines; src++)
+    for (src = 0, dst = 0; lines[src]; src++)
     {
-        if (lines[src] == NULL || strstr(lines[src], token) == NULL)
-            result[dst++] = lines[src];
+        if (strstr(lines[src], token) == NULL)
+            lines[dst++] = lines[src];
+        else
+            free(lines[src]);
     }
-
-    return result;
+    lines[dst] = NULL;
 }
 #endif

@@ -1066,7 +1048,7 @@ setup_config(void)
     conflines = readfile(conf_file);

     snprintf(repltok, sizeof(repltok), "max_connections = %d", n_connections);
-    conflines = replace_token(conflines, "#max_connections = 100", repltok);
+    replace_token(conflines, "#max_connections = 100", repltok);

     if ((n_buffers * (BLCKSZ / 1024)) % 1024 == 0)
         snprintf(repltok, sizeof(repltok), "shared_buffers = %dMB",
@@ -1074,7 +1056,7 @@ setup_config(void)
     else
         snprintf(repltok, sizeof(repltok), "shared_buffers = %dkB",
                  n_buffers * (BLCKSZ / 1024));
-    conflines = replace_token(conflines, "#shared_buffers = 128MB", repltok);
+    replace_token(conflines, "#shared_buffers = 128MB", repltok);

 #ifdef HAVE_UNIX_SOCKETS
     snprintf(repltok, sizeof(repltok), "#unix_socket_directories = '%s'",
@@ -1082,38 +1064,38 @@ setup_config(void)
 #else
     snprintf(repltok, sizeof(repltok), "#unix_socket_directories = ''");
 #endif
-    conflines = replace_token(conflines, "#unix_socket_directories = '/tmp'",
+    replace_token(conflines, "#unix_socket_directories = '/tmp'",
                               repltok);

 #if DEF_PGPORT != 5432
     snprintf(repltok, sizeof(repltok), "#port = %d", DEF_PGPORT);
-    conflines = replace_token(conflines, "#port = 5432", repltok);
+    replace_token(conflines, "#port = 5432", repltok);
 #endif

     /* set default max_wal_size and min_wal_size */
     snprintf(repltok, sizeof(repltok), "min_wal_size = %s",
              pretty_wal_size(DEFAULT_MIN_WAL_SEGS));
-    conflines = replace_token(conflines, "#min_wal_size = 80MB", repltok);
+    replace_token(conflines, "#min_wal_size = 80MB", repltok);

     snprintf(repltok, sizeof(repltok), "max_wal_size = %s",
              pretty_wal_size(DEFAULT_MAX_WAL_SEGS));
-    conflines = replace_token(conflines, "#max_wal_size = 1GB", repltok);
+    replace_token(conflines, "#max_wal_size = 1GB", repltok);

     snprintf(repltok, sizeof(repltok), "lc_messages = '%s'",
              escape_quotes(lc_messages));
-    conflines = replace_token(conflines, "#lc_messages = 'C'", repltok);
+    replace_token(conflines, "#lc_messages = 'C'", repltok);

     snprintf(repltok, sizeof(repltok), "lc_monetary = '%s'",
              escape_quotes(lc_monetary));
-    conflines = replace_token(conflines, "#lc_monetary = 'C'", repltok);
+    replace_token(conflines, "#lc_monetary = 'C'", repltok);

     snprintf(repltok, sizeof(repltok), "lc_numeric = '%s'",
              escape_quotes(lc_numeric));
-    conflines = replace_token(conflines, "#lc_numeric = 'C'", repltok);
+    replace_token(conflines, "#lc_numeric = 'C'", repltok);

     snprintf(repltok, sizeof(repltok), "lc_time = '%s'",
              escape_quotes(lc_time));
-    conflines = replace_token(conflines, "#lc_time = 'C'", repltok);
+    replace_token(conflines, "#lc_time = 'C'", repltok);

     switch (locale_date_order(lc_time))
     {
@@ -1128,12 +1110,12 @@ setup_config(void)
             strcpy(repltok, "datestyle = 'iso, mdy'");
             break;
     }
-    conflines = replace_token(conflines, "#datestyle = 'iso, mdy'", repltok);
+    replace_token(conflines, "#datestyle = 'iso, mdy'", repltok);

     snprintf(repltok, sizeof(repltok),
              "default_text_search_config = 'pg_catalog.%s'",
              escape_quotes(default_text_search_config));
-    conflines = replace_token(conflines,
+    replace_token(conflines,
                               "#default_text_search_config = 'pg_catalog.simple'",
                               repltok);

@@ -1141,46 +1123,46 @@ setup_config(void)
     {
         snprintf(repltok, sizeof(repltok), "timezone = '%s'",
                  escape_quotes(default_timezone));
-        conflines = replace_token(conflines, "#timezone = 'GMT'", repltok);
+        replace_token(conflines, "#timezone = 'GMT'", repltok);
         snprintf(repltok, sizeof(repltok), "log_timezone = '%s'",
                  escape_quotes(default_timezone));
-        conflines = replace_token(conflines, "#log_timezone = 'GMT'", repltok);
+        replace_token(conflines, "#log_timezone = 'GMT'", repltok);
     }

     snprintf(repltok, sizeof(repltok), "dynamic_shared_memory_type = %s",
              dynamic_shared_memory_type);
-    conflines = replace_token(conflines, "#dynamic_shared_memory_type = posix",
+    replace_token(conflines, "#dynamic_shared_memory_type = posix",
                               repltok);

 #if DEFAULT_BACKEND_FLUSH_AFTER > 0
     snprintf(repltok, sizeof(repltok), "#backend_flush_after = %dkB",
              DEFAULT_BACKEND_FLUSH_AFTER * (BLCKSZ / 1024));
-    conflines = replace_token(conflines, "#backend_flush_after = 0",
+    replace_token(conflines, "#backend_flush_after = 0",
                               repltok);
 #endif

 #if DEFAULT_BGWRITER_FLUSH_AFTER > 0
     snprintf(repltok, sizeof(repltok), "#bgwriter_flush_after = %dkB",
              DEFAULT_BGWRITER_FLUSH_AFTER * (BLCKSZ / 1024));
-    conflines = replace_token(conflines, "#bgwriter_flush_after = 0",
+    replace_token(conflines, "#bgwriter_flush_after = 0",
                               repltok);
 #endif

 #if DEFAULT_CHECKPOINT_FLUSH_AFTER > 0
     snprintf(repltok, sizeof(repltok), "#checkpoint_flush_after = %dkB",
              DEFAULT_CHECKPOINT_FLUSH_AFTER * (BLCKSZ / 1024));
-    conflines = replace_token(conflines, "#checkpoint_flush_after = 0",
+    replace_token(conflines, "#checkpoint_flush_after = 0",
                               repltok);
 #endif

 #ifndef USE_PREFETCH
-    conflines = replace_token(conflines,
+    replace_token(conflines,
                               "#effective_io_concurrency = 1",
                               "#effective_io_concurrency = 0");
 #endif

 #ifdef WIN32
-    conflines = replace_token(conflines,
+    replace_token(conflines,
                               "#update_process_title = on",
                               "#update_process_title = off");
 #endif
@@ -1194,7 +1176,7 @@ setup_config(void)
         (strcmp(authmethodhost, "md5") == 0 &&
          strcmp(authmethodlocal, "scram-sha-256") != 0))
     {
-        conflines = replace_token(conflines,
+        replace_token(conflines,
                                   "#password_encryption = scram-sha-256",
                                   "password_encryption = md5");
     }
@@ -1207,7 +1189,7 @@ setup_config(void)
      */
     if (pg_dir_create_mode == PG_DIR_MODE_GROUP)
     {
-        conflines = replace_token(conflines,
+        replace_token(conflines,
                                   "#log_file_mode = 0600",
                                   "log_file_mode = 0640");
     }
@@ -1248,9 +1230,9 @@ setup_config(void)
     conflines = readfile(hba_file);

 #ifndef HAVE_UNIX_SOCKETS
-    conflines = filter_lines_with_token(conflines, "@remove-line-for-nolocal@");
+    remove_lines_with_token(conflines, "@remove-line-for-nolocal@");
 #else
-    conflines = replace_token(conflines, "@remove-line-for-nolocal@", "");
+    replace_token(conflines, "@remove-line-for-nolocal@", "");
 #endif

 #ifdef HAVE_IPV6
@@ -1287,33 +1269,33 @@ setup_config(void)
         if (err != 0 ||
             getaddrinfo("::1", NULL, &hints, &gai_result) != 0)
         {
-            conflines = replace_token(conflines,
+            replace_token(conflines,
                                       "host    all             all             ::1",
                                       "#host    all             all             ::1");
-            conflines = replace_token(conflines,
+            replace_token(conflines,
                                       "host    replication     all             ::1",
                                       "#host    replication     all             ::1");
         }
     }
 #else                            /* !HAVE_IPV6 */
     /* If we didn't compile IPV6 support at all, always comment it out */
-    conflines = replace_token(conflines,
+    replace_token(conflines,
                               "host    all             all             ::1",
                               "#host    all             all             ::1");
-    conflines = replace_token(conflines,
+    replace_token(conflines,
                               "host    replication     all             ::1",
                               "#host    replication     all             ::1");
 #endif                            /* HAVE_IPV6 */

     /* Replace default authentication methods */
-    conflines = replace_token(conflines,
+    replace_token(conflines,
                               "@authmethodhost@",
                               authmethodhost);
-    conflines = replace_token(conflines,
+    replace_token(conflines,
                               "@authmethodlocal@",
                               authmethodlocal);

-    conflines = replace_token(conflines,
+    replace_token(conflines,
                               "@authcomment@",
                               (strcmp(authmethodlocal, "trust") == 0 || strcmp(authmethodhost, "trust") == 0) ?
AUTHTRUST_WARNING: ""); 

@@ -1382,27 +1364,27 @@ bootstrap_template1(void)
     /* Substitute for various symbols used in the BKI file */

     sprintf(buf, "%d", NAMEDATALEN);
-    bki_lines = replace_token(bki_lines, "NAMEDATALEN", buf);
+    replace_token(bki_lines, "NAMEDATALEN", buf);

     sprintf(buf, "%d", (int) sizeof(Pointer));
-    bki_lines = replace_token(bki_lines, "SIZEOF_POINTER", buf);
+    replace_token(bki_lines, "SIZEOF_POINTER", buf);

-    bki_lines = replace_token(bki_lines, "ALIGNOF_POINTER",
+    replace_token(bki_lines, "ALIGNOF_POINTER",
                               (sizeof(Pointer) == 4) ? "i" : "d");

-    bki_lines = replace_token(bki_lines, "FLOAT8PASSBYVAL",
+    replace_token(bki_lines, "FLOAT8PASSBYVAL",
                               FLOAT8PASSBYVAL ? "true" : "false");

-    bki_lines = replace_token(bki_lines, "POSTGRES",
+    replace_token(bki_lines, "POSTGRES",
                               escape_quotes_bki(username));

-    bki_lines = replace_token(bki_lines, "ENCODING",
+    replace_token(bki_lines, "ENCODING",
                               encodingid_to_string(encodingid));

-    bki_lines = replace_token(bki_lines, "LC_COLLATE",
+    replace_token(bki_lines, "LC_COLLATE",
                               escape_quotes_bki(lc_collate));

-    bki_lines = replace_token(bki_lines, "LC_CTYPE",
+    replace_token(bki_lines, "LC_CTYPE",
                               escape_quotes_bki(lc_ctype));

     /* Also ensure backend isn't confused by this environment var: */
@@ -1624,7 +1606,8 @@ setup_privileges(FILE *cmdfd)
 {
     char      **line;
     char      **priv_lines;
-    static char *privileges_setup[] = {
+    int i;
+    static const char * const privileges_setup[] = {
         "UPDATE pg_class "
         "  SET relacl = (SELECT array_agg(a.acl) FROM "
         " (SELECT E'=r/\"$POSTGRES_SUPERUSERNAME\"' as acl "
@@ -1759,10 +1742,19 @@ setup_privileges(FILE *cmdfd)
         NULL
     };

-    priv_lines = replace_token(privileges_setup, "$POSTGRES_SUPERUSERNAME",
+    priv_lines = (char**) pg_malloc(lengthof(privileges_setup) * sizeof(char*));
+    for (i = 0; privileges_setup[i]; i++)
+        priv_lines[i] = pg_strdup(privileges_setup[i]);
+    priv_lines[i] = NULL;
+
+    replace_token(priv_lines, "$POSTGRES_SUPERUSERNAME",
                                escape_quotes(username));
     for (line = priv_lines; *line != NULL; line++)
+    {
         PG_CMD_PUTS(*line);
+        free(*line);
+    }
+    free(priv_lines);
 }

 /*

pgsql-bugs by date:

Previous
From: Andres Freund
Date:
Subject: Re: Report a potential memory leak in setup_config()
Next
From: wliang@stu.xidian.edu.cn
Date:
Subject: Re: Report a potential memory leak in setup_config()