commit 07bd2fbfdc0b2a3575d35e7c455db5c297145da5
Author: Dmitry Goncharov <dgoncharov@users.sf.net>
Date:   Sun Sep 4 22:42:10 2022 -0400

    [SV 63045] Load again each intact unloaded shared object.
    
    If makefile rules do not update an unloaded shared object, load it again.
    Avoid double loading of the same object when the setup routine returns -1.
    
    * makeint.h (load_file): Take struct file * instead of const char **.
    (unload_file): Change return type to int.
    * filedef.h (struct file): Add flag unloaded.
    * commands.c (execute_file_commands): Set file->unloaded and reset
    file->loaded when a shared object is successfully unloaded.
    * read.c (eval): Set file->loaded and reset file->unloaded when a shared
    object is loaded. Enter to the db each loaded file whos routine succeeded.
    * main.c (main): Load again any unloaded shared object that was not updated.
    This fixes the crash.
    * load.c (load_file): Take struct file * instead of const char **.
    Check file->loaded to avoid double loading the same object.
    Fix a memory leak of string loaded.
    Return -1, rather than 1, if the object is already loaded. This fixes
    double loading of the same object when the setup routine returns -1.
    (load_object): Add a log message.
    (unload_file): Return an error code on dlclose failure.
    * tests/scripts/features/loadapi: Add new tests.

diff --git a/src/commands.c b/src/commands.c
index 3dd1953f..1813e321 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -463,9 +463,15 @@ execute_file_commands (struct file *file)
   set_file_variables (file, file->stem);
 
   /* If this is a loaded dynamic object, unload it before remaking.
-     Some systems don't support overwriting a loaded object.  */
-  if (file->loaded)
-    unload_file (file->name);
+     Some systems don't support overwriting a loaded object.
+     Keep its name is .LOADED. This object will be rebuilt and loaded again.
+     If rebuilding or loading again fail, then make will exit and it won't
+     matter that this name is present in .LOADED.  */
+  if (file->loaded && unload_file (file->name) == 0)
+    {
+      file->loaded = 0;
+      file->unloaded = 1;
+    }
 
   /* Start the commands running.  */
   new_job (file);
diff --git a/src/filedef.h b/src/filedef.h
index 1eb8f61c..304a07f6 100644
--- a/src/filedef.h
+++ b/src/filedef.h
@@ -84,6 +84,7 @@ struct file
     unsigned int builtin:1;     /* True if the file is a builtin rule. */
     unsigned int precious:1;    /* Non-0 means don't delete file on quit */
     unsigned int loaded:1;      /* True if the file is a loaded object. */
+    unsigned int unloaded:1;    /* True if this loaded object was unloaded. */
     unsigned int low_resolution_time:1; /* Nonzero if this file's time stamp
                                            has only one-second resolution.  */
     unsigned int tried_implicit:1; /* Nonzero if have searched
diff --git a/src/load.c b/src/load.c
index f86027d3..d72679c6 100644
--- a/src/load.c
+++ b/src/load.c
@@ -89,6 +89,7 @@ load_object (const floc *flocp, int noerror, const char *ldname,
             OS (error, flocp, "%s", err);
           return NULL;
         }
+      DB (DB_VERBOSE, (_("Loaded shared object %s\n"), ldname));
 
       /* Assert that the GPL license symbol is defined.  */
       symp = (load_func_t) dlsym (dlp, "plugin_is_GPL_compatible");
@@ -119,19 +120,20 @@ load_object (const floc *flocp, int noerror, const char *ldname,
 }
 
 int
-load_file (const floc *flocp, const char **ldname, int noerror)
+load_file (const floc *flocp, void *f, int noerror)
 {
-  size_t nmlen = strlen (*ldname);
+  struct file *file = (struct file *) f;
+  const char *ldname = file->name;
+  size_t nmlen = strlen (ldname);
   char *new = alloca (nmlen + CSTRLEN (SYMBOL_EXTENSION) + 1);
   char *symname = NULL;
-  char *loaded;
   const char *fp;
   int r;
   load_func_t symp;
 
   /* Break the input into an object file name and a symbol name.  If no symbol
      name was provided, compute one from the object file name.  */
-  fp = strchr (*ldname, '(');
+  fp = strchr (ldname, '(');
   if (fp)
     {
       const char *ep;
@@ -142,16 +144,15 @@ load_file (const floc *flocp, const char **ldname, int noerror)
       ep = strchr (fp+1, ')');
       if (ep && ep[1] == '\0')
         {
-          size_t l = fp - *ldname;;
+          size_t l = fp - ldname;
 
           ++fp;
           if (fp == ep)
-            OS (fatal, flocp, _("Empty symbol name for load: %s"), *ldname);
-
+            OS (fatal, flocp, _("Empty symbol name for load: %s"), ldname);
           /* Make a copy of the ldname part.  */
-          memcpy (new, *ldname, l);
+          memcpy (new, ldname, l);
           new[l] = '\0';
-          *ldname = new;
+          ldname = new;
           nmlen = l;
 
           /* Make a copy of the symbol name part.  */
@@ -162,21 +163,23 @@ load_file (const floc *flocp, const char **ldname, int noerror)
     }
 
   /* Add this name to the string cache so it can be reused later.  */
-  *ldname = strcache_add (*ldname);
+  ldname = file->name = strcache_add (ldname);
 
-  /* If this object has been loaded, we're done.  */
-  loaded = allocated_variable_expand ("$(.LOADED)");
-  fp = strstr (loaded, *ldname);
-  r = fp && (fp==loaded || fp[-1]==' ') && (fp[nmlen]=='\0' || fp[nmlen]==' ');
-  if (r)
-    goto exit;
+  /* If this object has been loaded, we're done.
+   * Return -1 to ensure make does not rebuilt this shared object.
+   * A rebuilt, if required, took place when this object was loaded.  */
+  {
+    struct file *ff = lookup_file (ldname);
+    if (ff && ff->loaded)
+      return -1;
+  }
 
   /* If we didn't find a symbol name yet, construct it from the ldname.  */
   if (! symname)
     {
       char *p = new;
 
-      fp = strrchr (*ldname, '/');
+      fp = strrchr (ldname, '/');
 #ifdef HAVE_DOS_PATHS
       if (fp)
         {
@@ -186,13 +189,13 @@ load_file (const floc *flocp, const char **ldname, int noerror)
             fp = fp2;
         }
       else
-        fp = strrchr (*ldname, '\\');
+        fp = strrchr (ldname, '\\');
       /* The (improbable) case of d:foo.  */
       if (fp && *fp && fp[1] == ':')
         fp++;
 #endif
       if (!fp)
-        fp = *ldname;
+        fp = ldname;
       else
         ++fp;
       while (isalnum ((unsigned char) *fp) || *fp == '_')
@@ -201,10 +204,10 @@ load_file (const floc *flocp, const char **ldname, int noerror)
       symname = new;
     }
 
-  DB (DB_VERBOSE, (_("Loading symbol %s from %s\n"), symname, *ldname));
+  DB (DB_VERBOSE, (_("Loading symbol %s from %s\n"), symname, ldname));
 
   /* Load it!  */
-  symp = load_object (flocp, noerror, *ldname, symname);
+  symp = load_object (flocp, noerror, ldname, symname);
   if (! symp)
     return 0;
 
@@ -214,42 +217,49 @@ load_file (const floc *flocp, const char **ldname, int noerror)
   /* If it succeeded, add the load file to the loaded variable.  */
   if (r > 0)
     {
-      size_t loadlen = strlen (loaded);
-      char *newval = alloca (loadlen + strlen (*ldname) + 2);
+      size_t loadlen;
+      char *newval;
+      char *loaded = allocated_variable_expand ("$(.LOADED)");
+      loadlen = strlen (loaded);
+      newval = alloca (loadlen + strlen (ldname) + 2);
       /* Don't add a space if it's empty.  */
       if (loadlen)
         {
           memcpy (newval, loaded, loadlen);
           newval[loadlen++] = ' ';
         }
-      strcpy (&newval[loadlen], *ldname);
+      strcpy (&newval[loadlen], ldname);
       do_variable_definition (flocp, ".LOADED", newval, o_default, f_simple, 0);
+      free (loaded);
     }
 
- exit:
-  free (loaded);
   return r;
 }
 
-void
+int
 unload_file (const char *name)
 {
+  int rc = 0;
   struct load_list *d;
 
   for (d = loaded_syms; d != NULL; d = d->next)
     if (streq (d->name, name) && d->dlp)
       {
-        if (dlclose (d->dlp))
+        DB (DB_VERBOSE, (_("Unloading shared object %s\n"), name));
+        rc = dlclose (d->dlp);
+        if (rc)
           perror_with_name ("dlclose: ", d->name);
-        d->dlp = NULL;
+        else
+          d->dlp = NULL;
         break;
       }
+  return rc;
 }
 
 #else
 
 int
-load_file (const floc *flocp, const char **ldname UNUSED, int noerror)
+load_file (const floc *flocp, void *f UNUSED, int noerror)
 {
   if (! noerror)
     O (fatal, flocp,
@@ -258,7 +268,7 @@ load_file (const floc *flocp, const char **ldname UNUSED, int noerror)
   return 0;
 }
 
-void
+int
 unload_file (const char *name UNUSED)
 {
   O (fatal, NILF, "INTERNAL: Cannot unload when load is not supported");
diff --git a/src/main.c b/src/main.c
index 1deb926d..6d600205 100644
--- a/src/main.c
+++ b/src/main.c
@@ -2429,6 +2429,25 @@ main (int argc, char **argv, char **envp)
           break;
 
         case us_none:
+          {
+             /* Load again any unloaded shared object.
+              * Do not re-exec to have that shared object loaded.
+              * A re-exec would cause an infinite loop, because the shared
+              * object was not updated.  */
+            struct goaldep *d;
+
+            for (d = read_files; d; d = d->next)
+              if (d->file->unloaded)
+                {
+                  struct file *f = d->file;
+                  /* Load the file.  0 means failure.  */
+                  if (load_file (&d->floc, f, 0) == 0)
+                    OS (fatal, &d->floc, _("%s: failed to load"), f->name);
+                  f->unloaded = 0;
+                  f->loaded = 1;
+                }
+          }
+
           /* No makefiles needed to be updated.  If we couldn't read some
              included file that we care about, fail.  */
           if (0)
diff --git a/src/makeint.h b/src/makeint.h
index dd45218d..aa94a2f7 100644
--- a/src/makeint.h
+++ b/src/makeint.h
@@ -616,8 +616,8 @@ int guile_gmake_setup (const floc *flocp);
 
 /* Loadable object support.  Sets to the strcached name of the loaded file.  */
 typedef int (*load_func_t)(const floc *flocp);
-int load_file (const floc *flocp, const char **filename, int noerror);
-void unload_file (const char *name);
+int load_file (const floc *flocp, void *file, int noerror);
+int unload_file (const char *name);
 
 /* Maintainer mode support */
 #ifdef MAKE_MAINTAINER_MODE
diff --git a/src/read.c b/src/read.c
index 97e71bac..18829fd8 100644
--- a/src/read.c
+++ b/src/read.c
@@ -954,12 +954,24 @@ eval (struct ebuffer *ebuf, int set_default)
               struct nameseq *next = files->next;
               const char *name = files->name;
               struct goaldep *deps;
+              struct file *f;
               int r;
 
-              /* Load the file.  0 means failure.  */
-              r = load_file (&ebuf->floc, &name, noerror);
-              if (! r && ! noerror)
-                OS (fatal, &ebuf->floc, _("%s: failed to load"), name);
+              {
+                struct file file = {0};
+                file.name = name;
+                /* Load the file.  0 means failure.  */
+                r = load_file (&ebuf->floc, &file, noerror);
+                if (! r && ! noerror)
+                  OS (fatal, &ebuf->floc, _("%s: failed to load"), name);
+                name = file.name;
+              }
+
+              f = lookup_file (name);
+              if (f == 0)
+                f = enter_file (name);
+              f->loaded = 1;
+              f->unloaded = 0;
 
               free_ns (files);
               files = next;
@@ -968,14 +980,11 @@ eval (struct ebuffer *ebuf, int set_default)
               if (r == -1)
                 continue;
 
-              /* It succeeded, so add it to the list "to be rebuilt".  */
+              /* Succeeded or failed, add it to the list "to be rebuilt".  */
               deps = alloc_goaldep ();
               deps->next = read_files;
               read_files = deps;
-              deps->file = lookup_file (name);
-              if (deps->file == 0)
-                deps->file = enter_file (name);
-              deps->file->loaded = 1;
+              deps->file = f;
             }
 
           continue;
