public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCH] C++ify gdb/common/environ.c
@ 2017-04-13  4:05 Sergio Durigan Junior
  2017-04-15 18:51 ` [PATCH v2] " Sergio Durigan Junior
                   ` (6 more replies)
  0 siblings, 7 replies; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-04-13  4:05 UTC (permalink / raw)
  To: GDB Patches; +Cc: Sergio Durigan Junior

Disclaimer: this patch depends on
<https://sourceware.org/ml/gdb-patches/2017-03/msg00551.html> to be
fully testable.

As part of the preparation necessary for my upcoming task, I'd like to
propose that we turn gdb_environ into a class.  The approach taken
here is simple: the class gdb_environ contains everything that is
needed to manipulate the environment variables.  These variables are
stored in two data structures: an unordered_set, good because lookups
are O(n), and an std::vector<char *>, which can be converted to a
'char **' and passed as argument to functions that need it.

The usage has not changed much.  One has to initialize the class and
call its member functions if any operation needs to be done with the
environment variables.

As mentioned before, this is a preparation for an upcoming work that I
will be posting in the next few weeks or so.  For that work, I'll
probably create another data structure that will contain all the
environment variables that were set by the user using the 'set
environment' command, because I'll need access to them.  This will be
much easier with the class-ification of gdb_environ.

As noted, this has been regression-tested with the new version of
environ.exp and no regressions were found.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	* charset.c (find_charset_names): Use a
	std::unique_ptr<gdb_environ>.  Update code to use
	'iconv_env' object.  Remove call to 'free_environ'.
	* common/environ.c: Include <utility>.
	(make_environ): Delete function.
	(gdb_environ::init_environ): New function.
	(free_environ): Delete function.
	(gdb_environ::clear_environ): New function.
	(gdb_environ::gdb_environ): New function.
	(init_environ): Delete function.
	(gdb_environ::~gdb_environ): New function.
	(gdb_environ::reinit_environ): Likewise.
	(gdb_environ::get_in_environ): Likewise.
	(environ_vector): Delete function.
	(unset_in_environ_vector): New function.
	(set_in_environ): Delete function.
	(gdb_environ::set_in_environ): New function.
	(unset_in_environ): Delete function.
	(gdb_environ::unset_in_environ): New function.
	(gdb_environ::get_environ_char_vector): Likewise.
	(gdb_environ::get_environ_vector): Likewise.
	* common/environ.h: Include <unordered_map> and <vector>.
	(struct gdb_environ): Delete; transform into...
	(class gdb_environ): ... this class.
	(free_environ): Delete prototype.
	(init_environ, get_in_environ, set_in_environ, unset_in_environ,
	environ_vector): Likewise.
	* infcmd.c (run_command_1): Update code to call
	'get_environ_char_vector' from 'gdb_environ' class.
	(environment_info): Update code to call methods from 'gdb_environ'
	class.
	(unset_environment_command): Likewise.
	(path_info): Likewise.
	(path_command): Likewise.
	* inferior.c (free_inferior): Delete call to 'free_environ'.
	(add_inferior_silent): Create 'inf->environment' from
	std::unique_ptr<gdb_environ>.
	* inferior.h: Include "environ.h".
	(struct inferior) <environment>: Change type from 'struct
	gdb_environ' to 'std::unique_ptr<gdb_environ>'.
	* mi/mi-cmd-env.c (mi_cmd_env_path): Update code to call
	methods from 'gdb_environ' class.
	(mi_cmd_inferior_tty_show): Use 'std::unique_ptr<gdb_environ>'.
	(_initialize_mi_cmd_env): Update code to call methods from
	'gdb_environ' class.
	* solib.c (solib_find_1): Likewise
---
 gdb/charset.c        |  11 +--
 gdb/common/environ.c | 228 +++++++++++++++++++++++++--------------------------
 gdb/common/environ.h |  62 +++++++++-----
 gdb/infcmd.c         |  31 ++++---
 gdb/inferior.c       |   4 +-
 gdb/inferior.h       |   5 +-
 gdb/mi/mi-cmd-env.c  |  13 ++-
 gdb/solib.c          |   8 +-
 8 files changed, 187 insertions(+), 175 deletions(-)

diff --git a/gdb/charset.c b/gdb/charset.c
index f55e482..a5ab383 100644
--- a/gdb/charset.c
+++ b/gdb/charset.c
@@ -794,16 +794,14 @@ find_charset_names (void)
   int err, status;
   int fail = 1;
   int flags;
-  struct gdb_environ *iconv_env;
+  std::unique_ptr<gdb_environ> iconv_env (new gdb_environ);
   char *iconv_program;
 
   /* Older iconvs, e.g. 2.2.2, don't omit the intro text if stdout is
      not a tty.  We need to recognize it and ignore it.  This text is
      subject to translation, so force LANGUAGE=C.  */
-  iconv_env = make_environ ();
-  init_environ (iconv_env);
-  set_in_environ (iconv_env, "LANGUAGE", "C");
-  set_in_environ (iconv_env, "LC_ALL", "C");
+  iconv_env->set_in_environ ("LANGUAGE", "C");
+  iconv_env->set_in_environ ("LC_ALL", "C");
 
   child = pex_init (PEX_USE_PIPES, "iconv", NULL);
 
@@ -827,7 +825,7 @@ find_charset_names (void)
   /* Note that we simply ignore errors here.  */
   if (!pex_run_in_environment (child, flags,
 			       args[0], const_cast<char **> (args),
-			       environ_vector (iconv_env),
+			       iconv_env->get_environ_char_vector (),
 			       NULL, NULL, &err))
     {
       FILE *in = pex_read_output (child, 0);
@@ -901,7 +899,6 @@ find_charset_names (void)
 
   xfree (iconv_program);
   pex_free (child);
-  free_environ (iconv_env);
 
   if (fail)
     {
diff --git a/gdb/common/environ.c b/gdb/common/environ.c
index 3145d01..bd3c964 100644
--- a/gdb/common/environ.c
+++ b/gdb/common/environ.c
@@ -18,165 +18,161 @@
 #include "common-defs.h"
 #include "environ.h"
 #include <algorithm>
-\f
+#include <utility>
 
-/* Return a new environment object.  */
+/* See common/environ.h.  */
 
-struct gdb_environ *
-make_environ (void)
+void
+gdb_environ::init_environ (void)
 {
-  struct gdb_environ *e;
+  extern char **environ;
+
+  if (environ == NULL)
+    return;
+
+  for (int i = 0; environ[i] != NULL; ++i)
+    {
+      std::string v = std::string (environ[i]);
+      std::size_t pos = v.find ('=');
+      std::string var = v.substr (0, pos);
+      std::string value = v.substr (pos + 1);
 
-  e = XNEW (struct gdb_environ);
+      this->env.insert (std::make_pair (var, value));
+      this->environ_vector.push_back (xstrdup (environ[i]));
+    }
 
-  e->allocated = 10;
-  e->vector = (char **) xmalloc ((e->allocated + 1) * sizeof (char *));
-  e->vector[0] = 0;
-  return e;
+  /* The last element of the vector is always going to be NULL.  */
+  this->environ_vector.push_back (NULL);
 }
 
-/* Free an environment and all the strings in it.  */
+/* See common/environ.h.  */
 
 void
-free_environ (struct gdb_environ *e)
+gdb_environ::clear_environ (void)
 {
-  char **vector = e->vector;
+  this->env.clear ();
+  for (char *v : this->environ_vector)
+    xfree (v);
+  this->environ_vector.clear ();
+}
 
-  while (*vector)
-    xfree (*vector++);
+/* See common/environ.h.  */
 
-  xfree (e->vector);
-  xfree (e);
+gdb_environ::gdb_environ (void)
+{
+  this->init_environ ();
 }
 
-/* Copy the environment given to this process into E.
-   Also copies all the strings in it, so we can be sure
-   that all strings in these environments are safe to free.  */
+/* See common/environ.h.  */
 
-void
-init_environ (struct gdb_environ *e)
+gdb_environ::~gdb_environ (void)
 {
-  extern char **environ;
-  int i;
+  this->clear_environ ();
+}
 
-  if (environ == NULL)
-    return;
+/* See common/environ.h.  */
 
-  for (i = 0; environ[i]; i++) /*EMPTY */ ;
+void
+gdb_environ::reinit_environ (void)
+{
+  this->clear_environ ();
+  this->init_environ ();
+}
 
-  if (e->allocated < i)
+/* See common/environ.h.  */
+
+char *
+gdb_environ::get_in_environ (const char *var) const
+{
+  try
     {
-      e->allocated = std::max (i, e->allocated + 10);
-      e->vector = (char **) xrealloc ((char *) e->vector,
-				      (e->allocated + 1) * sizeof (char *));
+      return (char *) this->env.at (std::string (var)).c_str ();
     }
-
-  memcpy (e->vector, environ, (i + 1) * sizeof (char *));
-
-  while (--i >= 0)
+  catch (const std::out_of_range &ex)
     {
-      int len = strlen (e->vector[i]);
-      char *newobj = (char *) xmalloc (len + 1);
-
-      memcpy (newobj, e->vector[i], len + 1);
-      e->vector[i] = newobj;
+      return NULL;
     }
 }
 
-/* Return the vector of environment E.
-   This is used to get something to pass to execve.  */
-
-char **
-environ_vector (struct gdb_environ *e)
-{
-  return e->vector;
-}
-\f
-/* Return the value in environment E of variable VAR.  */
+/* Unset (delete) the environment variable VAR on the environment
+   vector V.  */
 
-char *
-get_in_environ (const struct gdb_environ *e, const char *var)
+static void
+unset_in_environ_vector (std::vector<char *> &v, const char *var)
 {
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
-
-  for (; (s = *vector) != NULL; vector++)
-    if (strncmp (s, var, len) == 0 && s[len] == '=')
-      return &s[len + 1];
-
-  return 0;
+  std::string match = std::string (var + std::string ("="));
+  const char *match_str = match.c_str ();
+
+  for (std::vector<char *>::const_iterator el = v.cbegin ();
+       el != v.cend ();
+       ++el)
+    if (startswith (*el, match_str))
+      {
+	xfree (*el);
+	v.erase (el);
+	break;
+      }
 }
 
-/* Store the value in E of VAR as VALUE.  */
+/* See common/environ.h.  */
 
 void
-set_in_environ (struct gdb_environ *e, const char *var, const char *value)
+gdb_environ::set_in_environ (const char *var, const char *value)
 {
-  int i;
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
+  std::unordered_map<std::string, std::string>::iterator el;
+  bool needs_update = true;
 
-  for (i = 0; (s = vector[i]) != NULL; i++)
-    if (strncmp (s, var, len) == 0 && s[len] == '=')
-      break;
+  el = this->env.find (var);
 
-  if (s == 0)
+  if (el != this->env.end ())
     {
-      if (i == e->allocated)
+      if (el->second.compare (value) == 0)
+	needs_update = false;
+      else
 	{
-	  e->allocated += 10;
-	  vector = (char **) xrealloc ((char *) vector,
-				       (e->allocated + 1) * sizeof (char *));
-	  e->vector = vector;
+	  /* If we found this item, it means that we have to update
+	     its value on the map.  */
+	  el->second = std::string (value);
+	  /* And we also have to update its value on the
+	     environ_vector.  For that, we just delete the item here
+	     and recreate it (with the new value) later.  */
+	  unset_in_environ_vector (this->environ_vector, var);
 	}
-      vector[i + 1] = 0;
     }
   else
-    xfree (s);
-
-  s = (char *) xmalloc (len + strlen (value) + 2);
-  strcpy (s, var);
-  strcat (s, "=");
-  strcat (s, value);
-  vector[i] = s;
-
-  /* This used to handle setting the PATH and GNUTARGET variables
-     specially.  The latter has been replaced by "set gnutarget"
-     (which has worked since GDB 4.11).  The former affects searching
-     the PATH to find SHELL, and searching the PATH to find the
-     argument of "symbol-file" or "exec-file".  Maybe we should have
-     some kind of "set exec-path" for that.  But in any event, having
-     "set env" affect anything besides the inferior is a bad idea.
-     What if we want to change the environment we pass to the program
-     without afecting GDB's behavior?  */
-
-  return;
+    {
+      this->env.insert (std::make_pair (std::string (var),
+					std::string (value)));
+    }
+
+  if (needs_update)
+    this->environ_vector.insert (this->environ_vector.end () - 1,
+				 xstrdup (std::string (var
+						       + std::string ("=")
+						       + value).c_str ()));
 }
 
-/* Remove the setting for variable VAR from environment E.  */
+/* See common/environ.h.  */
 
 void
-unset_in_environ (struct gdb_environ *e, const char *var)
+gdb_environ::unset_in_environ (const char *var)
 {
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
+  this->env.erase (var);
+  unset_in_environ_vector (this->environ_vector, var);
+}
 
-  for (; (s = *vector) != NULL; vector++)
-    {
-      if (strncmp (s, var, len) == 0 && s[len] == '=')
-	{
-	  xfree (s);
-	  /* Walk through the vector, shuffling args down by one, including
-	     the NULL terminator.  Can't use memcpy() here since the regions
-	     overlap, and memmove() might not be available.  */
-	  while ((vector[0] = vector[1]) != NULL)
-	    {
-	      vector++;
-	    }
-	  break;
-	}
-    }
+/* See common/environ.h.  */
+
+char **
+gdb_environ::get_environ_char_vector (void) const
+{
+  return (char **) &this->environ_vector[0];
+}
+
+/* See common/environ.h.  */
+
+std::vector<char *>
+gdb_environ::get_environ_vector (void) const
+{
+  return this->environ_vector;
 }
diff --git a/gdb/common/environ.h b/gdb/common/environ.h
index 3ace69e..c630425 100644
--- a/gdb/common/environ.h
+++ b/gdb/common/environ.h
@@ -17,33 +17,55 @@
 #if !defined (ENVIRON_H)
 #define ENVIRON_H 1
 
-/* We manipulate environments represented as these structures.  */
+#include <unordered_map>
+#include <vector>
 
-struct gdb_environ
-  {
-    /* Number of usable slots allocated in VECTOR.
-       VECTOR always has one slot not counted here,
-       to hold the terminating zero.  */
-    int allocated;
-    /* A vector of slots, ALLOCATED + 1 of them.
-       The first few slots contain strings "VAR=VALUE"
-       and the next one contains zero.
-       Then come some unused slots.  */
-    char **vector;
-  };
+/* Class that represents the environment variables as seeing by the
+   inferior.  */
 
-extern struct gdb_environ *make_environ (void);
+class gdb_environ
+{
+public:
+  /* Regular constructor and destructor.  */
+  gdb_environ ();
+  ~gdb_environ ();
 
-extern void free_environ (struct gdb_environ *);
+  /* Reinitialize the environment stored.  This is used when the user
+     wants to delete all environment variables added by him/her.  */
+  void reinit_environ (void);
 
-extern void init_environ (struct gdb_environ *);
+  /* Return the value in the environment for the variable VAR.  */
+  char *get_in_environ (const char *var) const;
 
-extern char *get_in_environ (const struct gdb_environ *, const char *);
+  /* Store VAR=VALUE in the environment.  */
+  void set_in_environ (const char *var, const char *value);
 
-extern void set_in_environ (struct gdb_environ *, const char *, const char *);
+  /* Unset VAR in environment.  */
+  void unset_in_environ (const char *var);
 
-extern void unset_in_environ (struct gdb_environ *, const char *);
+  /* Return the environment vector represented as a 'char **'.  */
+  char **get_environ_char_vector (void) const;
 
-extern char **environ_vector (struct gdb_environ *);
+  /* Return the environment vector.  */
+  std::vector<char *> get_environ_vector (void) const;
+
+private:
+  /* Helper function that initializes our data structures with the
+     environment variables.  */
+  void init_environ (void);
+
+  /* Helper function that clears our data structures.  */
+  void clear_environ (void);
+
+  /* A map representing the environment variables and their values.
+     It is easier to store them using a map because of the faster
+     lookups.  */
+  std::unordered_map<std::string, std::string> env;
+
+  /* A vector containing the environment variables.  This is useful
+     for when we need to obtain a 'char **' with all the existing
+     variables.  */
+  std::vector<char *> environ_vector;
+};
 
 #endif /* defined (ENVIRON_H) */
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index 22b2c7a..b1d0823 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -609,7 +609,8 @@ run_command_1 (char *args, int from_tty, int tbreak_at_main)
      the value now.  */
   run_target->to_create_inferior (run_target, exec_file,
 				  std::string (get_inferior_args ()),
-				  environ_vector (current_inferior ()->environment),
+				  current_inferior ()
+				  ->environment->get_environ_char_vector (),
 				  from_tty);
   /* to_create_inferior should push the target, so after this point we
      shouldn't refer to run_target again.  */
@@ -2141,7 +2142,7 @@ environment_info (char *var, int from_tty)
 {
   if (var)
     {
-      char *val = get_in_environ (current_inferior ()->environment, var);
+      char *val = current_inferior ()->environment->get_in_environ (var);
 
       if (val)
 	{
@@ -2159,11 +2160,12 @@ environment_info (char *var, int from_tty)
     }
   else
     {
-      char **vector = environ_vector (current_inferior ()->environment);
+      std::vector<char *> vector =
+	current_inferior ()->environment->get_environ_vector ();
 
-      while (*vector)
+      for (char *&elem : vector)
 	{
-	  puts_filtered (*vector++);
+	  puts_filtered (elem);
 	  puts_filtered ("\n");
 	}
     }
@@ -2225,10 +2227,10 @@ set_environment_command (char *arg, int from_tty)
       printf_filtered (_("Setting environment variable "
 			 "\"%s\" to null value.\n"),
 		       var);
-      set_in_environ (current_inferior ()->environment, var, "");
+      current_inferior ()->environment->set_in_environ (var, "");
     }
   else
-    set_in_environ (current_inferior ()->environment, var, val);
+    current_inferior ()->environment->set_in_environ (var, val);
   xfree (var);
 }
 
@@ -2240,13 +2242,10 @@ unset_environment_command (char *var, int from_tty)
       /* If there is no argument, delete all environment variables.
          Ask for confirmation if reading from the terminal.  */
       if (!from_tty || query (_("Delete all environment variables? ")))
-	{
-	  free_environ (current_inferior ()->environment);
-	  current_inferior ()->environment = make_environ ();
-	}
+	current_inferior ()->environment->reinit_environ ();
     }
   else
-    unset_in_environ (current_inferior ()->environment, var);
+    current_inferior ()->environment->unset_in_environ (var);
 }
 
 /* Handle the execution path (PATH variable).  */
@@ -2257,8 +2256,8 @@ static void
 path_info (char *args, int from_tty)
 {
   puts_filtered ("Executable and object file path: ");
-  puts_filtered (get_in_environ (current_inferior ()->environment,
-				 path_var_name));
+  puts_filtered (current_inferior ()->environment->get_in_environ
+		 (path_var_name));
   puts_filtered ("\n");
 }
 
@@ -2271,13 +2270,13 @@ path_command (char *dirname, int from_tty)
   const char *env;
 
   dont_repeat ();
-  env = get_in_environ (current_inferior ()->environment, path_var_name);
+  env = current_inferior ()->environment->get_in_environ (path_var_name);
   /* Can be null if path is not set.  */
   if (!env)
     env = "";
   exec_path = xstrdup (env);
   mod_path (dirname, &exec_path);
-  set_in_environ (current_inferior ()->environment, path_var_name, exec_path);
+  current_inferior ()->environment->set_in_environ (path_var_name, exec_path);
   xfree (exec_path);
   if (from_tty)
     path_info ((char *) NULL, from_tty);
diff --git a/gdb/inferior.c b/gdb/inferior.c
index 4ae265e..2529f1a 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -99,7 +99,6 @@ free_inferior (struct inferior *inf)
   inferior_free_data (inf);
   xfree (inf->args);
   xfree (inf->terminal);
-  free_environ (inf->environment);
   target_desc_info_free (inf->tdesc_info);
   xfree (inf->priv);
   xfree (inf);
@@ -147,8 +146,7 @@ add_inferior_silent (int pid)
       last->next = inf;
     }
 
-  inf->environment = make_environ ();
-  init_environ (inf->environment);
+  inf->environment = std::unique_ptr<gdb_environ> (new gdb_environ);
 
   inferior_alloc_data (inf);
 
diff --git a/gdb/inferior.h b/gdb/inferior.h
index bf06ac1..c1d2f87 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -40,6 +40,9 @@ struct target_desc_info;
 /* For struct frame_id.  */
 #include "frame.h"
 
+/* For gdb_environ.  */
+#include "environ.h"
+
 #include "progspace.h"
 #include "registry.h"
 
@@ -356,7 +359,7 @@ struct inferior
 
   /* Environment to use for running inferior,
      in format described in environ.h.  */
-  struct gdb_environ *environment;
+  std::unique_ptr<gdb_environ> environment;
 
   /* Nonzero if this child process was attached rather than
      forked.  */
diff --git a/gdb/mi/mi-cmd-env.c b/gdb/mi/mi-cmd-env.c
index 4093178..0a909c9 100644
--- a/gdb/mi/mi-cmd-env.c
+++ b/gdb/mi/mi-cmd-env.c
@@ -167,7 +167,7 @@ mi_cmd_env_path (const char *command, char **argv, int argc)
   else
     {
       /* Otherwise, get current path to modify.  */
-      env = get_in_environ (current_inferior ()->environment, path_var_name);
+      env = current_inferior ()->environment->get_in_environ (path_var_name);
 
       /* Can be null if path is not set.  */
       if (!env)
@@ -178,9 +178,9 @@ mi_cmd_env_path (const char *command, char **argv, int argc)
   for (i = argc - 1; i >= 0; --i)
     env_mod_path (argv[i], &exec_path);
 
-  set_in_environ (current_inferior ()->environment, path_var_name, exec_path);
+  current_inferior ()->environment->set_in_environ (path_var_name, exec_path);
   xfree (exec_path);
-  env = get_in_environ (current_inferior ()->environment, path_var_name);
+  env = current_inferior ()->environment->get_in_environ (path_var_name);
   uiout->field_string ("path", env);
 }
 
@@ -270,7 +270,7 @@ mi_cmd_inferior_tty_show (const char *command, char **argv, int argc)
 void 
 _initialize_mi_cmd_env (void)
 {
-  struct gdb_environ *environment;
+  std::unique_ptr<gdb_environ> environment (new gdb_environ);
   const char *env;
 
   /* We want original execution path to reset to, if desired later.
@@ -278,13 +278,10 @@ _initialize_mi_cmd_env (void)
      current_inferior ()->environment.  Also, there's no obvious
      place where this code can be moved such that it surely run
      before any code possibly mangles original PATH.  */
-  environment = make_environ ();
-  init_environ (environment);
-  env = get_in_environ (environment, path_var_name);
+  env = environment->get_in_environ (path_var_name);
 
   /* Can be null if path is not set.  */
   if (!env)
     env = "";
   orig_path = xstrdup (env);
-  free_environ (environment);
 }
diff --git a/gdb/solib.c b/gdb/solib.c
index af94383..6260df9 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -350,16 +350,16 @@ solib_find_1 (const char *in_pathname, int *fd, int is_solib)
 
   /* If not found, next search the inferior's $PATH environment variable.  */
   if (found_file < 0 && sysroot == NULL)
-    found_file = openp (get_in_environ (current_inferior ()->environment,
-					"PATH"),
+    found_file = openp (current_inferior ()->environment->get_in_environ
+			("PATH"),
 			OPF_TRY_CWD_FIRST | OPF_RETURN_REALPATH, in_pathname,
 			O_RDONLY | O_BINARY, &temp_pathname);
 
   /* If not found, and we're looking for a solib, next search the
      inferior's $LD_LIBRARY_PATH environment variable.  */
   if (is_solib && found_file < 0 && sysroot == NULL)
-    found_file = openp (get_in_environ (current_inferior ()->environment,
-					"LD_LIBRARY_PATH"),
+    found_file = openp (current_inferior ()->environment->get_in_environ
+			("LD_LIBRARY_PATH"),
 			OPF_TRY_CWD_FIRST | OPF_RETURN_REALPATH, in_pathname,
 			O_RDONLY | O_BINARY, &temp_pathname);
 
-- 
2.9.3

^ permalink raw reply	[flat|nested] 47+ messages in thread

* [PATCH v2] C++ify gdb/common/environ.c
  2017-04-13  4:05 [PATCH] C++ify gdb/common/environ.c Sergio Durigan Junior
@ 2017-04-15 18:51 ` Sergio Durigan Junior
  2017-04-15 21:22   ` Simon Marchi
  2017-04-16  5:09   ` Simon Marchi
  2017-04-18  3:03 ` [PATCH v3] " Sergio Durigan Junior
                   ` (5 subsequent siblings)
  6 siblings, 2 replies; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-04-15 18:51 UTC (permalink / raw)
  To: GDB Patches; +Cc: Sergio Durigan Junior

This is the version 2 of the patch.  It doesn't have any huge changes,
just the necessary to make it work with the recent conversion of
'struct inferior' to a class.

Disclaimer: this patch depends on
<https://sourceware.org/ml/gdb-patches/2017-03/msg00551.html> to be
fully testable.

As part of the preparation necessary for my upcoming task, I'd like to
propose that we turn gdb_environ into a class.  The approach taken
here is simple: the class gdb_environ contains everything that is
needed to manipulate the environment variables.  These variables are
stored in two data structures: an unordered_set, good because lookups
are O(n), and an std::vector<char *>, which can be converted to a
'char **' and passed as argument to functions that need it.

The usage has not changed much.  One has to initialize the class and
call its member functions if any operation needs to be done with the
environment variables.

As mentioned before, this is a preparation for an upcoming work that I
will be posting in the next few weeks or so.  For that work, I'll
probably create another data structure that will contain all the
environment variables that were set by the user using the 'set
environment' command, because I'll need access to them.  This will be
much easier with the class-ification of gdb_environ.

As noted, this has been regression-tested with the new version of
environ.exp and no regressions were found.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	* charset.c (find_charset_names): Use a
	std::unique_ptr<gdb_environ>.  Update code to use
	'iconv_env' object.  Remove call to 'free_environ'.
	* common/environ.c: Include <utility>.
	(make_environ): Delete function.
	(gdb_environ::init_environ): New function.
	(free_environ): Delete function.
	(gdb_environ::clear_environ): New function.
	(gdb_environ::gdb_environ): New function.
	(init_environ): Delete function.
	(gdb_environ::~gdb_environ): New function.
	(gdb_environ::reinit_environ): Likewise.
	(gdb_environ::get_in_environ): Likewise.
	(environ_vector): Delete function.
	(unset_in_environ_vector): New function.
	(set_in_environ): Delete function.
	(gdb_environ::set_in_environ): New function.
	(unset_in_environ): Delete function.
	(gdb_environ::unset_in_environ): New function.
	(gdb_environ::get_environ_char_vector): Likewise.
	(gdb_environ::get_environ_vector): Likewise.
	* common/environ.h: Include <unordered_map> and <vector>.
	(struct gdb_environ): Delete; transform into...
	(class gdb_environ): ... this class.
	(free_environ): Delete prototype.
	(init_environ, get_in_environ, set_in_environ, unset_in_environ,
	environ_vector): Likewise.
	* infcmd.c (run_command_1): Update code to call
	'get_environ_char_vector' from 'gdb_environ' class.
	(environment_info): Update code to call methods from 'gdb_environ'
	class.
	(unset_environment_command): Likewise.
	(path_info): Likewise.
	(path_command): Likewise.
	* inferior.c (inferior::~inferior): Delete call to 'free_environ'.
	(inferior::inferior): Creating new environment.
	* inferior.h: Include "environ.h".
	(class inferior) <environment>: Change type from 'struct
	gdb_environ' to 'std::unique_ptr<gdb_environ>'.
	* mi/mi-cmd-env.c (mi_cmd_env_path): Update code to call
	methods from 'gdb_environ' class.
	(mi_cmd_inferior_tty_show): Use 'std::unique_ptr<gdb_environ>'.
	(_initialize_mi_cmd_env): Update code to call methods from
	'gdb_environ' class.
	* solib.c (solib_find_1): Likewise
---
 gdb/charset.c        |  11 +--
 gdb/common/environ.c | 228 +++++++++++++++++++++++++--------------------------
 gdb/common/environ.h |  62 +++++++++-----
 gdb/infcmd.c         |  31 ++++---
 gdb/inferior.c       |   4 +-
 gdb/inferior.h       |   5 +-
 gdb/mi/mi-cmd-env.c  |  13 ++-
 gdb/solib.c          |   8 +-
 8 files changed, 187 insertions(+), 175 deletions(-)

diff --git a/gdb/charset.c b/gdb/charset.c
index f55e482..a5ab383 100644
--- a/gdb/charset.c
+++ b/gdb/charset.c
@@ -794,16 +794,14 @@ find_charset_names (void)
   int err, status;
   int fail = 1;
   int flags;
-  struct gdb_environ *iconv_env;
+  std::unique_ptr<gdb_environ> iconv_env (new gdb_environ);
   char *iconv_program;
 
   /* Older iconvs, e.g. 2.2.2, don't omit the intro text if stdout is
      not a tty.  We need to recognize it and ignore it.  This text is
      subject to translation, so force LANGUAGE=C.  */
-  iconv_env = make_environ ();
-  init_environ (iconv_env);
-  set_in_environ (iconv_env, "LANGUAGE", "C");
-  set_in_environ (iconv_env, "LC_ALL", "C");
+  iconv_env->set_in_environ ("LANGUAGE", "C");
+  iconv_env->set_in_environ ("LC_ALL", "C");
 
   child = pex_init (PEX_USE_PIPES, "iconv", NULL);
 
@@ -827,7 +825,7 @@ find_charset_names (void)
   /* Note that we simply ignore errors here.  */
   if (!pex_run_in_environment (child, flags,
 			       args[0], const_cast<char **> (args),
-			       environ_vector (iconv_env),
+			       iconv_env->get_environ_char_vector (),
 			       NULL, NULL, &err))
     {
       FILE *in = pex_read_output (child, 0);
@@ -901,7 +899,6 @@ find_charset_names (void)
 
   xfree (iconv_program);
   pex_free (child);
-  free_environ (iconv_env);
 
   if (fail)
     {
diff --git a/gdb/common/environ.c b/gdb/common/environ.c
index 3145d01..bd3c964 100644
--- a/gdb/common/environ.c
+++ b/gdb/common/environ.c
@@ -18,165 +18,161 @@
 #include "common-defs.h"
 #include "environ.h"
 #include <algorithm>
-\f
+#include <utility>
 
-/* Return a new environment object.  */
+/* See common/environ.h.  */
 
-struct gdb_environ *
-make_environ (void)
+void
+gdb_environ::init_environ (void)
 {
-  struct gdb_environ *e;
+  extern char **environ;
+
+  if (environ == NULL)
+    return;
+
+  for (int i = 0; environ[i] != NULL; ++i)
+    {
+      std::string v = std::string (environ[i]);
+      std::size_t pos = v.find ('=');
+      std::string var = v.substr (0, pos);
+      std::string value = v.substr (pos + 1);
 
-  e = XNEW (struct gdb_environ);
+      this->env.insert (std::make_pair (var, value));
+      this->environ_vector.push_back (xstrdup (environ[i]));
+    }
 
-  e->allocated = 10;
-  e->vector = (char **) xmalloc ((e->allocated + 1) * sizeof (char *));
-  e->vector[0] = 0;
-  return e;
+  /* The last element of the vector is always going to be NULL.  */
+  this->environ_vector.push_back (NULL);
 }
 
-/* Free an environment and all the strings in it.  */
+/* See common/environ.h.  */
 
 void
-free_environ (struct gdb_environ *e)
+gdb_environ::clear_environ (void)
 {
-  char **vector = e->vector;
+  this->env.clear ();
+  for (char *v : this->environ_vector)
+    xfree (v);
+  this->environ_vector.clear ();
+}
 
-  while (*vector)
-    xfree (*vector++);
+/* See common/environ.h.  */
 
-  xfree (e->vector);
-  xfree (e);
+gdb_environ::gdb_environ (void)
+{
+  this->init_environ ();
 }
 
-/* Copy the environment given to this process into E.
-   Also copies all the strings in it, so we can be sure
-   that all strings in these environments are safe to free.  */
+/* See common/environ.h.  */
 
-void
-init_environ (struct gdb_environ *e)
+gdb_environ::~gdb_environ (void)
 {
-  extern char **environ;
-  int i;
+  this->clear_environ ();
+}
 
-  if (environ == NULL)
-    return;
+/* See common/environ.h.  */
 
-  for (i = 0; environ[i]; i++) /*EMPTY */ ;
+void
+gdb_environ::reinit_environ (void)
+{
+  this->clear_environ ();
+  this->init_environ ();
+}
 
-  if (e->allocated < i)
+/* See common/environ.h.  */
+
+char *
+gdb_environ::get_in_environ (const char *var) const
+{
+  try
     {
-      e->allocated = std::max (i, e->allocated + 10);
-      e->vector = (char **) xrealloc ((char *) e->vector,
-				      (e->allocated + 1) * sizeof (char *));
+      return (char *) this->env.at (std::string (var)).c_str ();
     }
-
-  memcpy (e->vector, environ, (i + 1) * sizeof (char *));
-
-  while (--i >= 0)
+  catch (const std::out_of_range &ex)
     {
-      int len = strlen (e->vector[i]);
-      char *newobj = (char *) xmalloc (len + 1);
-
-      memcpy (newobj, e->vector[i], len + 1);
-      e->vector[i] = newobj;
+      return NULL;
     }
 }
 
-/* Return the vector of environment E.
-   This is used to get something to pass to execve.  */
-
-char **
-environ_vector (struct gdb_environ *e)
-{
-  return e->vector;
-}
-\f
-/* Return the value in environment E of variable VAR.  */
+/* Unset (delete) the environment variable VAR on the environment
+   vector V.  */
 
-char *
-get_in_environ (const struct gdb_environ *e, const char *var)
+static void
+unset_in_environ_vector (std::vector<char *> &v, const char *var)
 {
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
-
-  for (; (s = *vector) != NULL; vector++)
-    if (strncmp (s, var, len) == 0 && s[len] == '=')
-      return &s[len + 1];
-
-  return 0;
+  std::string match = std::string (var + std::string ("="));
+  const char *match_str = match.c_str ();
+
+  for (std::vector<char *>::const_iterator el = v.cbegin ();
+       el != v.cend ();
+       ++el)
+    if (startswith (*el, match_str))
+      {
+	xfree (*el);
+	v.erase (el);
+	break;
+      }
 }
 
-/* Store the value in E of VAR as VALUE.  */
+/* See common/environ.h.  */
 
 void
-set_in_environ (struct gdb_environ *e, const char *var, const char *value)
+gdb_environ::set_in_environ (const char *var, const char *value)
 {
-  int i;
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
+  std::unordered_map<std::string, std::string>::iterator el;
+  bool needs_update = true;
 
-  for (i = 0; (s = vector[i]) != NULL; i++)
-    if (strncmp (s, var, len) == 0 && s[len] == '=')
-      break;
+  el = this->env.find (var);
 
-  if (s == 0)
+  if (el != this->env.end ())
     {
-      if (i == e->allocated)
+      if (el->second.compare (value) == 0)
+	needs_update = false;
+      else
 	{
-	  e->allocated += 10;
-	  vector = (char **) xrealloc ((char *) vector,
-				       (e->allocated + 1) * sizeof (char *));
-	  e->vector = vector;
+	  /* If we found this item, it means that we have to update
+	     its value on the map.  */
+	  el->second = std::string (value);
+	  /* And we also have to update its value on the
+	     environ_vector.  For that, we just delete the item here
+	     and recreate it (with the new value) later.  */
+	  unset_in_environ_vector (this->environ_vector, var);
 	}
-      vector[i + 1] = 0;
     }
   else
-    xfree (s);
-
-  s = (char *) xmalloc (len + strlen (value) + 2);
-  strcpy (s, var);
-  strcat (s, "=");
-  strcat (s, value);
-  vector[i] = s;
-
-  /* This used to handle setting the PATH and GNUTARGET variables
-     specially.  The latter has been replaced by "set gnutarget"
-     (which has worked since GDB 4.11).  The former affects searching
-     the PATH to find SHELL, and searching the PATH to find the
-     argument of "symbol-file" or "exec-file".  Maybe we should have
-     some kind of "set exec-path" for that.  But in any event, having
-     "set env" affect anything besides the inferior is a bad idea.
-     What if we want to change the environment we pass to the program
-     without afecting GDB's behavior?  */
-
-  return;
+    {
+      this->env.insert (std::make_pair (std::string (var),
+					std::string (value)));
+    }
+
+  if (needs_update)
+    this->environ_vector.insert (this->environ_vector.end () - 1,
+				 xstrdup (std::string (var
+						       + std::string ("=")
+						       + value).c_str ()));
 }
 
-/* Remove the setting for variable VAR from environment E.  */
+/* See common/environ.h.  */
 
 void
-unset_in_environ (struct gdb_environ *e, const char *var)
+gdb_environ::unset_in_environ (const char *var)
 {
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
+  this->env.erase (var);
+  unset_in_environ_vector (this->environ_vector, var);
+}
 
-  for (; (s = *vector) != NULL; vector++)
-    {
-      if (strncmp (s, var, len) == 0 && s[len] == '=')
-	{
-	  xfree (s);
-	  /* Walk through the vector, shuffling args down by one, including
-	     the NULL terminator.  Can't use memcpy() here since the regions
-	     overlap, and memmove() might not be available.  */
-	  while ((vector[0] = vector[1]) != NULL)
-	    {
-	      vector++;
-	    }
-	  break;
-	}
-    }
+/* See common/environ.h.  */
+
+char **
+gdb_environ::get_environ_char_vector (void) const
+{
+  return (char **) &this->environ_vector[0];
+}
+
+/* See common/environ.h.  */
+
+std::vector<char *>
+gdb_environ::get_environ_vector (void) const
+{
+  return this->environ_vector;
 }
diff --git a/gdb/common/environ.h b/gdb/common/environ.h
index 3ace69e..c630425 100644
--- a/gdb/common/environ.h
+++ b/gdb/common/environ.h
@@ -17,33 +17,55 @@
 #if !defined (ENVIRON_H)
 #define ENVIRON_H 1
 
-/* We manipulate environments represented as these structures.  */
+#include <unordered_map>
+#include <vector>
 
-struct gdb_environ
-  {
-    /* Number of usable slots allocated in VECTOR.
-       VECTOR always has one slot not counted here,
-       to hold the terminating zero.  */
-    int allocated;
-    /* A vector of slots, ALLOCATED + 1 of them.
-       The first few slots contain strings "VAR=VALUE"
-       and the next one contains zero.
-       Then come some unused slots.  */
-    char **vector;
-  };
+/* Class that represents the environment variables as seeing by the
+   inferior.  */
 
-extern struct gdb_environ *make_environ (void);
+class gdb_environ
+{
+public:
+  /* Regular constructor and destructor.  */
+  gdb_environ ();
+  ~gdb_environ ();
 
-extern void free_environ (struct gdb_environ *);
+  /* Reinitialize the environment stored.  This is used when the user
+     wants to delete all environment variables added by him/her.  */
+  void reinit_environ (void);
 
-extern void init_environ (struct gdb_environ *);
+  /* Return the value in the environment for the variable VAR.  */
+  char *get_in_environ (const char *var) const;
 
-extern char *get_in_environ (const struct gdb_environ *, const char *);
+  /* Store VAR=VALUE in the environment.  */
+  void set_in_environ (const char *var, const char *value);
 
-extern void set_in_environ (struct gdb_environ *, const char *, const char *);
+  /* Unset VAR in environment.  */
+  void unset_in_environ (const char *var);
 
-extern void unset_in_environ (struct gdb_environ *, const char *);
+  /* Return the environment vector represented as a 'char **'.  */
+  char **get_environ_char_vector (void) const;
 
-extern char **environ_vector (struct gdb_environ *);
+  /* Return the environment vector.  */
+  std::vector<char *> get_environ_vector (void) const;
+
+private:
+  /* Helper function that initializes our data structures with the
+     environment variables.  */
+  void init_environ (void);
+
+  /* Helper function that clears our data structures.  */
+  void clear_environ (void);
+
+  /* A map representing the environment variables and their values.
+     It is easier to store them using a map because of the faster
+     lookups.  */
+  std::unordered_map<std::string, std::string> env;
+
+  /* A vector containing the environment variables.  This is useful
+     for when we need to obtain a 'char **' with all the existing
+     variables.  */
+  std::vector<char *> environ_vector;
+};
 
 #endif /* defined (ENVIRON_H) */
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index 22b2c7a..b1d0823 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -609,7 +609,8 @@ run_command_1 (char *args, int from_tty, int tbreak_at_main)
      the value now.  */
   run_target->to_create_inferior (run_target, exec_file,
 				  std::string (get_inferior_args ()),
-				  environ_vector (current_inferior ()->environment),
+				  current_inferior ()
+				  ->environment->get_environ_char_vector (),
 				  from_tty);
   /* to_create_inferior should push the target, so after this point we
      shouldn't refer to run_target again.  */
@@ -2141,7 +2142,7 @@ environment_info (char *var, int from_tty)
 {
   if (var)
     {
-      char *val = get_in_environ (current_inferior ()->environment, var);
+      char *val = current_inferior ()->environment->get_in_environ (var);
 
       if (val)
 	{
@@ -2159,11 +2160,12 @@ environment_info (char *var, int from_tty)
     }
   else
     {
-      char **vector = environ_vector (current_inferior ()->environment);
+      std::vector<char *> vector =
+	current_inferior ()->environment->get_environ_vector ();
 
-      while (*vector)
+      for (char *&elem : vector)
 	{
-	  puts_filtered (*vector++);
+	  puts_filtered (elem);
 	  puts_filtered ("\n");
 	}
     }
@@ -2225,10 +2227,10 @@ set_environment_command (char *arg, int from_tty)
       printf_filtered (_("Setting environment variable "
 			 "\"%s\" to null value.\n"),
 		       var);
-      set_in_environ (current_inferior ()->environment, var, "");
+      current_inferior ()->environment->set_in_environ (var, "");
     }
   else
-    set_in_environ (current_inferior ()->environment, var, val);
+    current_inferior ()->environment->set_in_environ (var, val);
   xfree (var);
 }
 
@@ -2240,13 +2242,10 @@ unset_environment_command (char *var, int from_tty)
       /* If there is no argument, delete all environment variables.
          Ask for confirmation if reading from the terminal.  */
       if (!from_tty || query (_("Delete all environment variables? ")))
-	{
-	  free_environ (current_inferior ()->environment);
-	  current_inferior ()->environment = make_environ ();
-	}
+	current_inferior ()->environment->reinit_environ ();
     }
   else
-    unset_in_environ (current_inferior ()->environment, var);
+    current_inferior ()->environment->unset_in_environ (var);
 }
 
 /* Handle the execution path (PATH variable).  */
@@ -2257,8 +2256,8 @@ static void
 path_info (char *args, int from_tty)
 {
   puts_filtered ("Executable and object file path: ");
-  puts_filtered (get_in_environ (current_inferior ()->environment,
-				 path_var_name));
+  puts_filtered (current_inferior ()->environment->get_in_environ
+		 (path_var_name));
   puts_filtered ("\n");
 }
 
@@ -2271,13 +2270,13 @@ path_command (char *dirname, int from_tty)
   const char *env;
 
   dont_repeat ();
-  env = get_in_environ (current_inferior ()->environment, path_var_name);
+  env = current_inferior ()->environment->get_in_environ (path_var_name);
   /* Can be null if path is not set.  */
   if (!env)
     env = "";
   exec_path = xstrdup (env);
   mod_path (dirname, &exec_path);
-  set_in_environ (current_inferior ()->environment, path_var_name, exec_path);
+  current_inferior ()->environment->set_in_environ (path_var_name, exec_path);
   xfree (exec_path);
   if (from_tty)
     path_info ((char *) NULL, from_tty);
diff --git a/gdb/inferior.c b/gdb/inferior.c
index 327590a..054a91a 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -100,7 +100,6 @@ inferior::~inferior ()
   inferior_free_data (inf);
   xfree (inf->args);
   xfree (inf->terminal);
-  free_environ (inf->environment);
   target_desc_info_free (inf->tdesc_info);
   xfree (inf->priv);
 }
@@ -108,10 +107,9 @@ inferior::~inferior ()
 inferior::inferior (int pid_)
   : num (++highest_inferior_num),
     pid (pid_),
-    environment (make_environ ()),
+    environment (new gdb_environ),
     registry_data ()
 {
-  init_environ (this->environment);
   inferior_alloc_data (this);
 }
 
diff --git a/gdb/inferior.h b/gdb/inferior.h
index b5eb3d1..e48d5cb 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -42,6 +42,9 @@ struct continuation;
 /* For struct frame_id.  */
 #include "frame.h"
 
+/* For gdb_environ.  */
+#include "environ.h"
+
 #include "progspace.h"
 #include "registry.h"
 
@@ -362,7 +365,7 @@ public:
 
   /* Environment to use for running inferior,
      in format described in environ.h.  */
-  gdb_environ *environment = NULL;
+  std::unique_ptr<gdb_environ> environment;
 
   /* True if this child process was attached rather than forked.  */
   bool attach_flag = false;
diff --git a/gdb/mi/mi-cmd-env.c b/gdb/mi/mi-cmd-env.c
index 4093178..0a909c9 100644
--- a/gdb/mi/mi-cmd-env.c
+++ b/gdb/mi/mi-cmd-env.c
@@ -167,7 +167,7 @@ mi_cmd_env_path (const char *command, char **argv, int argc)
   else
     {
       /* Otherwise, get current path to modify.  */
-      env = get_in_environ (current_inferior ()->environment, path_var_name);
+      env = current_inferior ()->environment->get_in_environ (path_var_name);
 
       /* Can be null if path is not set.  */
       if (!env)
@@ -178,9 +178,9 @@ mi_cmd_env_path (const char *command, char **argv, int argc)
   for (i = argc - 1; i >= 0; --i)
     env_mod_path (argv[i], &exec_path);
 
-  set_in_environ (current_inferior ()->environment, path_var_name, exec_path);
+  current_inferior ()->environment->set_in_environ (path_var_name, exec_path);
   xfree (exec_path);
-  env = get_in_environ (current_inferior ()->environment, path_var_name);
+  env = current_inferior ()->environment->get_in_environ (path_var_name);
   uiout->field_string ("path", env);
 }
 
@@ -270,7 +270,7 @@ mi_cmd_inferior_tty_show (const char *command, char **argv, int argc)
 void 
 _initialize_mi_cmd_env (void)
 {
-  struct gdb_environ *environment;
+  std::unique_ptr<gdb_environ> environment (new gdb_environ);
   const char *env;
 
   /* We want original execution path to reset to, if desired later.
@@ -278,13 +278,10 @@ _initialize_mi_cmd_env (void)
      current_inferior ()->environment.  Also, there's no obvious
      place where this code can be moved such that it surely run
      before any code possibly mangles original PATH.  */
-  environment = make_environ ();
-  init_environ (environment);
-  env = get_in_environ (environment, path_var_name);
+  env = environment->get_in_environ (path_var_name);
 
   /* Can be null if path is not set.  */
   if (!env)
     env = "";
   orig_path = xstrdup (env);
-  free_environ (environment);
 }
diff --git a/gdb/solib.c b/gdb/solib.c
index af94383..6260df9 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -350,16 +350,16 @@ solib_find_1 (const char *in_pathname, int *fd, int is_solib)
 
   /* If not found, next search the inferior's $PATH environment variable.  */
   if (found_file < 0 && sysroot == NULL)
-    found_file = openp (get_in_environ (current_inferior ()->environment,
-					"PATH"),
+    found_file = openp (current_inferior ()->environment->get_in_environ
+			("PATH"),
 			OPF_TRY_CWD_FIRST | OPF_RETURN_REALPATH, in_pathname,
 			O_RDONLY | O_BINARY, &temp_pathname);
 
   /* If not found, and we're looking for a solib, next search the
      inferior's $LD_LIBRARY_PATH environment variable.  */
   if (is_solib && found_file < 0 && sysroot == NULL)
-    found_file = openp (get_in_environ (current_inferior ()->environment,
-					"LD_LIBRARY_PATH"),
+    found_file = openp (current_inferior ()->environment->get_in_environ
+			("LD_LIBRARY_PATH"),
 			OPF_TRY_CWD_FIRST | OPF_RETURN_REALPATH, in_pathname,
 			O_RDONLY | O_BINARY, &temp_pathname);
 
-- 
2.9.3

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v2] C++ify gdb/common/environ.c
  2017-04-15 18:51 ` [PATCH v2] " Sergio Durigan Junior
@ 2017-04-15 21:22   ` Simon Marchi
  2017-04-18  2:49     ` Sergio Durigan Junior
  2017-04-16  5:09   ` Simon Marchi
  1 sibling, 1 reply; 47+ messages in thread
From: Simon Marchi @ 2017-04-15 21:22 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: GDB Patches

On 2017-04-15 14:50, Sergio Durigan Junior wrote:
> diff --git a/gdb/charset.c b/gdb/charset.c
> index f55e482..a5ab383 100644
> --- a/gdb/charset.c
> +++ b/gdb/charset.c
> @@ -794,16 +794,14 @@ find_charset_names (void)
>    int err, status;
>    int fail = 1;
>    int flags;
> -  struct gdb_environ *iconv_env;
> +  std::unique_ptr<gdb_environ> iconv_env (new gdb_environ);

Can it be allocated on the stack?

>  void
> -set_in_environ (struct gdb_environ *e, const char *var, const char 
> *value)
> +gdb_environ::set_in_environ (const char *var, const char *value)
>  {
> -  int i;
> -  int len = strlen (var);
> -  char **vector = e->vector;
> -  char *s;
> +  std::unordered_map<std::string, std::string>::iterator el;
> +  bool needs_update = true;
> 
> -  for (i = 0; (s = vector[i]) != NULL; i++)
> -    if (strncmp (s, var, len) == 0 && s[len] == '=')
> -      break;
> +  el = this->env.find (var);
> 
> -  if (s == 0)
> +  if (el != this->env.end ())
>      {
> -      if (i == e->allocated)
> +      if (el->second.compare (value) == 0)

I think operator== will do what you want.

If you did a return here, you wouldn't need the needs_update variable.  
I don't mind though, some people prefer a single return point.

> diff --git a/gdb/common/environ.h b/gdb/common/environ.h
> index 3ace69e..c630425 100644
> --- a/gdb/common/environ.h
> +++ b/gdb/common/environ.h
> @@ -17,33 +17,55 @@
>  #if !defined (ENVIRON_H)
>  #define ENVIRON_H 1
> 
> -/* We manipulate environments represented as these structures.  */
> +#include <unordered_map>
> +#include <vector>

In the interface of gdb_environ, I think you could get rid of the 
"environ" in method names.  You can also remove the (void) for methods 
without parameters.

> 
> -struct gdb_environ
> -  {
> -    /* Number of usable slots allocated in VECTOR.
> -       VECTOR always has one slot not counted here,
> -       to hold the terminating zero.  */
> -    int allocated;
> -    /* A vector of slots, ALLOCATED + 1 of them.
> -       The first few slots contain strings "VAR=VALUE"
> -       and the next one contains zero.
> -       Then come some unused slots.  */
> -    char **vector;
> -  };
> +/* Class that represents the environment variables as seeing by the

s/as seeing/as seen/

> +   inferior.  */
> 
> -extern struct gdb_environ *make_environ (void);
> +class gdb_environ
> +{
> +public:
> +  /* Regular constructor and destructor.  */
> +  gdb_environ ();
> +  ~gdb_environ ();
> 
> -extern void free_environ (struct gdb_environ *);
> +  /* Reinitialize the environment stored.  This is used when the user
> +     wants to delete all environment variables added by him/her.  */
> +  void reinit_environ (void);
> 
> -extern void init_environ (struct gdb_environ *);
> +  /* Return the value in the environment for the variable VAR.  */
> +  char *get_in_environ (const char *var) const;

Looking at the users, I think that this could return const char *.  It 
would also be good to mention that the pointer is valid as long as the 
corresponding variable environment is not removed/replaced.

It could also return something based on std::string, but I don't know 
how good it would be:

1. returning const std::string &: can't return "NULL".
2. returning gdb::optional<std::string>: it works but makes a copy of 
the string, maybe it's ok since there isn't a ton of that.  It would 
however make the return value usable even after the corresponding 
variable is removed from the env.
3. returning gdb::optional<const std::string &>: I don't think it can be 
done.
4. returning gdb::optional<std::reference_wrapper<const std::string>>: 
it works, but it gets a bit ugly.

One thing is sure, since you are constructing some std::string inside 
the methods, you could change the parameters in the interface from const 
char * to const std::string &.  With the implicit string constructor 
from const char *, the users won't have to be changed, but it will be 
possible to pass an std::string directly.

> 
> -extern char *get_in_environ (const struct gdb_environ *, const char 
> *);
> +  /* Store VAR=VALUE in the environment.  */
> +  void set_in_environ (const char *var, const char *value);
> 
> -extern void set_in_environ (struct gdb_environ *, const char *, const 
> char *);
> +  /* Unset VAR in environment.  */
> +  void unset_in_environ (const char *var);
> 
> -extern void unset_in_environ (struct gdb_environ *, const char *);
> +  /* Return the environment vector represented as a 'char **'.  */
> +  char **get_environ_char_vector (void) const;
> 
> -extern char **environ_vector (struct gdb_environ *);
> +  /* Return the environment vector.  */
> +  std::vector<char *> get_environ_vector (void) const;

Return a const reference, and change the (sole) user of this method to 
get a reference too?

> @@ -2159,11 +2160,12 @@ environment_info (char *var, int from_tty)
>      }
>    else
>      {
> -      char **vector = environ_vector (current_inferior 
> ()->environment);
> +      std::vector<char *> vector =
> +	current_inferior ()->environment->get_environ_vector ();
> 
> -      while (*vector)
> +      for (char *&elem : vector)

Is there advantage of doing

   for (char *&elem : vector)

vs

   for (char *elem : vector)

?

> diff --git a/gdb/inferior.h b/gdb/inferior.h
> index b5eb3d1..e48d5cb 100644
> --- a/gdb/inferior.h
> +++ b/gdb/inferior.h
> @@ -42,6 +42,9 @@ struct continuation;
>  /* For struct frame_id.  */
>  #include "frame.h"
> 
> +/* For gdb_environ.  */
> +#include "environ.h"
> +

You can remove the "struct gdb_environ;" above this.

>  #include "progspace.h"
>  #include "registry.h"
> 
> @@ -362,7 +365,7 @@ public:
> 
>    /* Environment to use for running inferior,
>       in format described in environ.h.  */
> -  gdb_environ *environment = NULL;
> +  std::unique_ptr<gdb_environ> environment;

Can this be simply

   gdb_environ environment;

?

> @@ -270,7 +270,7 @@ mi_cmd_inferior_tty_show (const char *command,
> char **argv, int argc)
>  void
>  _initialize_mi_cmd_env (void)
>  {
> -  struct gdb_environ *environment;
> +  std::unique_ptr<gdb_environ> environment (new gdb_environ);

Again, can it be allocated on the stack?

Thanks,

Simon

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v2] C++ify gdb/common/environ.c
  2017-04-15 18:51 ` [PATCH v2] " Sergio Durigan Junior
  2017-04-15 21:22   ` Simon Marchi
@ 2017-04-16  5:09   ` Simon Marchi
  2017-04-16 17:32     ` Sergio Durigan Junior
  1 sibling, 1 reply; 47+ messages in thread
From: Simon Marchi @ 2017-04-16  5:09 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: GDB Patches

On 2017-04-15 14:50, Sergio Durigan Junior wrote:
> As part of the preparation necessary for my upcoming task, I'd like to
> propose that we turn gdb_environ into a class.  The approach taken
> here is simple: the class gdb_environ contains everything that is
> needed to manipulate the environment variables.  These variables are
> stored in two data structures: an unordered_set, good because lookups
> are O(n), and an std::vector<char *>, which can be converted to a
> 'char **' and passed as argument to functions that need it.

Forgot to mention (probably a typo) that lookups in the map are O(1) on 
average.  If they were O(n), it wouldn't be better than looking up in a 
vector :).

At least that's what it says here:

[1] http://en.cppreference.com/w/cpp/container/unordered_map/at

Simon

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v2] C++ify gdb/common/environ.c
  2017-04-16  5:09   ` Simon Marchi
@ 2017-04-16 17:32     ` Sergio Durigan Junior
  0 siblings, 0 replies; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-04-16 17:32 UTC (permalink / raw)
  To: Simon Marchi; +Cc: GDB Patches

On Sunday, April 16 2017, Simon Marchi wrote:

> On 2017-04-15 14:50, Sergio Durigan Junior wrote:
>> As part of the preparation necessary for my upcoming task, I'd like to
>> propose that we turn gdb_environ into a class.  The approach taken
>> here is simple: the class gdb_environ contains everything that is
>> needed to manipulate the environment variables.  These variables are
>> stored in two data structures: an unordered_set, good because lookups
>> are O(n), and an std::vector<char *>, which can be converted to a
>> 'char **' and passed as argument to functions that need it.
>
> Forgot to mention (probably a typo) that lookups in the map are O(1)
> on average.  If they were O(n), it wouldn't be better than looking up
> in a vector :).

You're right, they're O(1).  I was thinking about the vector when I
wrote.  Thanks for the correction!

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v2] C++ify gdb/common/environ.c
  2017-04-15 21:22   ` Simon Marchi
@ 2017-04-18  2:49     ` Sergio Durigan Junior
  0 siblings, 0 replies; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-04-18  2:49 UTC (permalink / raw)
  To: Simon Marchi; +Cc: GDB Patches

Thanks for the review, Simon.  I don't know why I didn't receive this
message on my INBOX, so it took a little bit until I saw it on
gdb-patches.

On Saturday, April 15 2017, Simon Marchi wrote:

> On 2017-04-15 14:50, Sergio Durigan Junior wrote:
>> diff --git a/gdb/charset.c b/gdb/charset.c
>> index f55e482..a5ab383 100644
>> --- a/gdb/charset.c
>> +++ b/gdb/charset.c
>> @@ -794,16 +794,14 @@ find_charset_names (void)
>>    int err, status;
>>    int fail = 1;
>>    int flags;
>> -  struct gdb_environ *iconv_env;
>> +  std::unique_ptr<gdb_environ> iconv_env (new gdb_environ);
>
> Can it be allocated on the stack?

Yeah, it can.  It's probably an overkill to use a unique_ptr here
because the object will be short-lived anyway.  I'll change that.

>>  void
>> -set_in_environ (struct gdb_environ *e, const char *var, const char
>> *value)
>> +gdb_environ::set_in_environ (const char *var, const char *value)
>>  {
>> -  int i;
>> -  int len = strlen (var);
>> -  char **vector = e->vector;
>> -  char *s;
>> +  std::unordered_map<std::string, std::string>::iterator el;
>> +  bool needs_update = true;
>>
>> -  for (i = 0; (s = vector[i]) != NULL; i++)
>> -    if (strncmp (s, var, len) == 0 && s[len] == '=')
>> -      break;
>> +  el = this->env.find (var);
>>
>> -  if (s == 0)
>> +  if (el != this->env.end ())
>>      {
>> -      if (i == e->allocated)
>> +      if (el->second.compare (value) == 0)
>
> I think operator== will do what you want.

Good catch, thanks.

> If you did a return here, you wouldn't need the needs_update variable.
> I don't mind though, some people prefer a single return point.

No, you're totally right, I overlooked this detail.  Changed to a
return.

>> diff --git a/gdb/common/environ.h b/gdb/common/environ.h
>> index 3ace69e..c630425 100644
>> --- a/gdb/common/environ.h
>> +++ b/gdb/common/environ.h
>> @@ -17,33 +17,55 @@
>>  #if !defined (ENVIRON_H)
>>  #define ENVIRON_H 1
>>
>> -/* We manipulate environments represented as these structures.  */
>> +#include <unordered_map>
>> +#include <vector>
>
> In the interface of gdb_environ, I think you could get rid of the
> "environ" in method names.  You can also remove the (void) for methods
> without parameters.

Indeed, the "environ" in the names doesn't make sense anymore.

As for removing (void)...  I personally like to make it explicit, but I
understand we're living in other times now (C++11 and all).  I'll
remove.

>>
>> -struct gdb_environ
>> -  {
>> -    /* Number of usable slots allocated in VECTOR.
>> -       VECTOR always has one slot not counted here,
>> -       to hold the terminating zero.  */
>> -    int allocated;
>> -    /* A vector of slots, ALLOCATED + 1 of them.
>> -       The first few slots contain strings "VAR=VALUE"
>> -       and the next one contains zero.
>> -       Then come some unused slots.  */
>> -    char **vector;
>> -  };
>> +/* Class that represents the environment variables as seeing by the
>
> s/as seeing/as seen/

Fixed.

>> +   inferior.  */
>>
>> -extern struct gdb_environ *make_environ (void);
>> +class gdb_environ
>> +{
>> +public:
>> +  /* Regular constructor and destructor.  */
>> +  gdb_environ ();
>> +  ~gdb_environ ();
>>
>> -extern void free_environ (struct gdb_environ *);
>> +  /* Reinitialize the environment stored.  This is used when the user
>> +     wants to delete all environment variables added by him/her.  */
>> +  void reinit_environ (void);
>>
>> -extern void init_environ (struct gdb_environ *);
>> +  /* Return the value in the environment for the variable VAR.  */
>> +  char *get_in_environ (const char *var) const;
>
> Looking at the users, I think that this could return const char *.  It
> would also be good to mention that the pointer is valid as long as the
> corresponding variable environment is not removed/replaced.

Fair enough.  I constified the return type of the 'get' method, and
changed the callers accordingly.  I've also added the 

> It could also return something based on std::string, but I don't know
> how good it would be:
>
> 1. returning const std::string &: can't return "NULL".
> 2. returning gdb::optional<std::string>: it works but makes a copy of
> the string, maybe it's ok since there isn't a ton of that.  It would
> however make the return value usable even after the corresponding
> variable is removed from the env.
> 3. returning gdb::optional<const std::string &>: I don't think it can
> be done.
> 4. returning gdb::optional<std::reference_wrapper<const std::string>>:
> it works, but it gets a bit ugly.

Ahm, I'm not sure.  I mean, I know the rationale here (you're trying to
make sure that the return value only exists for as long as the
environment variable is there), but I don't think it's necessary to
overcomplicate things for now.  IMHO, a 'const char *' + the updated
comment are enough.

> One thing is sure, since you are constructing some std::string inside
> the methods, you could change the parameters in the interface from
> const char * to const std::string &.  With the implicit string
> constructor from const char *, the users won't have to be changed, but
> it will be possible to pass an std::string directly.

I confess this hasn't even crossed my mind.  Thanks, I adopted the idea.

>>
>> -extern char *get_in_environ (const struct gdb_environ *, const char
>> *);
>> +  /* Store VAR=VALUE in the environment.  */
>> +  void set_in_environ (const char *var, const char *value);
>>
>> -extern void set_in_environ (struct gdb_environ *, const char *,
>> const char *);
>> +  /* Unset VAR in environment.  */
>> +  void unset_in_environ (const char *var);
>>
>> -extern void unset_in_environ (struct gdb_environ *, const char *);
>> +  /* Return the environment vector represented as a 'char **'.  */
>> +  char **get_environ_char_vector (void) const;
>>
>> -extern char **environ_vector (struct gdb_environ *);
>> +  /* Return the environment vector.  */
>> +  std::vector<char *> get_environ_vector (void) const;
>
> Return a const reference, and change the (sole) user of this method to
> get a reference too?

Heh, I had done that before I got to this part of the e-mail.  Thanks.

>> @@ -2159,11 +2160,12 @@ environment_info (char *var, int from_tty)
>>      }
>>    else
>>      {
>> -      char **vector = environ_vector (current_inferior
>> ()->environment);
>> +      std::vector<char *> vector =
>> +	current_inferior ()->environment->get_environ_vector ();
>>
>> -      while (*vector)
>> +      for (char *&elem : vector)
>
> Is there advantage of doing
>
>   for (char *&elem : vector)
>
> vs
>
>   for (char *elem : vector)
>
> ?

No, this is just a brain fart.  I wrote this code when I was also
dealing with the fork-inferior sharing stuff, and you can see the same
pattern there.  Fixed.

>> diff --git a/gdb/inferior.h b/gdb/inferior.h
>> index b5eb3d1..e48d5cb 100644
>> --- a/gdb/inferior.h
>> +++ b/gdb/inferior.h
>> @@ -42,6 +42,9 @@ struct continuation;
>>  /* For struct frame_id.  */
>>  #include "frame.h"
>>
>> +/* For gdb_environ.  */
>> +#include "environ.h"
>> +
>
> You can remove the "struct gdb_environ;" above this.

Fixed.

>
>>  #include "progspace.h"
>>  #include "registry.h"
>>
>> @@ -362,7 +365,7 @@ public:
>>
>>    /* Environment to use for running inferior,
>>       in format described in environ.h.  */
>> -  gdb_environ *environment = NULL;
>> +  std::unique_ptr<gdb_environ> environment;
>
> Can this be simply
>
>   gdb_environ environment;
>
> ?
>
>> @@ -270,7 +270,7 @@ mi_cmd_inferior_tty_show (const char *command,
>> char **argv, int argc)
>>  void
>>  _initialize_mi_cmd_env (void)
>>  {
>> -  struct gdb_environ *environment;
>> +  std::unique_ptr<gdb_environ> environment (new gdb_environ);
>
> Again, can it be allocated on the stack?

Yes, already done that.  Thanks.

I'll send v3 soon.

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 47+ messages in thread

* [PATCH v3] C++ify gdb/common/environ.c
  2017-04-13  4:05 [PATCH] C++ify gdb/common/environ.c Sergio Durigan Junior
  2017-04-15 18:51 ` [PATCH v2] " Sergio Durigan Junior
@ 2017-04-18  3:03 ` Sergio Durigan Junior
  2017-04-19  4:56   ` Simon Marchi
  2017-04-19 18:14   ` Pedro Alves
  2017-06-14 19:22 ` [PATCH v4] " Sergio Durigan Junior
                   ` (4 subsequent siblings)
  6 siblings, 2 replies; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-04-18  3:03 UTC (permalink / raw)
  To: GDB Patches; +Cc: Simon Marchi, Sergio Durigan Junior

Changes from v2 (mostly from Simon's review):

- Not using std::unique_ptr for gdb_environ anymore.

- Got rid of "_environ" suffixes from the names of the methods.

- Remove (void) from methods without parameters.

- Constify return of 'get' method.

- Change 'const char *' for 'const std::string &' on methods'
  parameters.

- Returning a 'const std::vector<char *> &' on the 'get_vector'
  method.

- Typos, and other minor nits.



Disclaimer: this patch depends on
<https://sourceware.org/ml/gdb-patches/2017-03/msg00551.html> to be
fully testable.

As part of the preparation necessary for my upcoming task, I'd like to
propose that we turn gdb_environ into a class.  The approach taken
here is simple: the class gdb_environ contains everything that is
needed to manipulate the environment variables.  These variables are
stored in two data structures: an unordered_set, good because lookups
are O(n), and an std::vector<char *>, which can be converted to a
'char **' and passed as argument to functions that need it.

The usage has not changed much.  One has to initialize the class and
call its member functions if any operation needs to be done with the
environment variables.

As mentioned before, this is a preparation for an upcoming work that I
will be posting in the next few weeks or so.  For that work, I'll
probably create another data structure that will contain all the
environment variables that were set by the user using the 'set
environment' command, because I'll need access to them.  This will be
much easier with the class-ification of gdb_environ.

As noted, this has been regression-tested with the new version of
environ.exp and no regressions were found.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	* charset.c (find_charset_names): Declare object 'iconv_env'.
	Update code to use 'iconv_env' object.  Remove call to
	'free_environ'.
	* common/environ.c: Include <utility>.
	(make_environ): Delete function.
	(gdb_environ::init): New function.
	(free_environ): Delete function.
	(gdb_environ::clear): New function.
	(gdb_environ::gdb_environ): New function.
	(init_environ): Delete function.
	(gdb_environ::~gdb_environ): New function.
	(gdb_environ::reinit): Likewise.
	(gdb_environ::get): Likewise.
	(environ_vector): Delete function.
	(unset_in_vector): New function.
	(set_in_environ): Delete function.
	(gdb_environ::set): New function.
	(unset_in_environ): Delete function.
	(gdb_environ::unset): New function.
	(gdb_environ::get_char_vector): Likewise.
	(gdb_environ::get_vector): Likewise.
	* common/environ.h: Include <unordered_map> and <vector>.
	(struct gdb_environ): Delete; transform into...
	(class gdb_environ): ... this class.
	(free_environ): Delete prototype.
	(init_environ, get_in_environ, set_in_environ, unset_in_environ,
	environ_vector): Likewise.
	* infcmd.c (run_command_1): Update code to call
	'get_char_vector' from 'gdb_environ' class.
	(environment_info): Update code to call methods from 'gdb_environ'
	class.
	(unset_environment_command): Likewise.
	(path_info): Likewise.
	(path_command): Likewise.
	* inferior.c (inferior::~inferior): Delete call to 'free_environ'.
	* inferior.h: Remove forward declaration of 'struct gdb_environ'.
	Include "environ.h".
	(class inferior) <environment>: Change type from 'struct
	gdb_environ' to 'gdb_environ'.
	* mi/mi-cmd-env.c (mi_cmd_env_path): Update code to call
	methods from 'gdb_environ' class.
	(mi_cmd_inferior_tty_show): Use 'gdb_environ'.
	(_initialize_mi_cmd_env): Update code to call methods from
	'gdb_environ' class.
	* solib.c (solib_find_1): Likewise
---
 gdb/charset.c        |  11 +--
 gdb/common/environ.c | 223 ++++++++++++++++++++++++---------------------------
 gdb/common/environ.h |  64 ++++++++++-----
 gdb/infcmd.c         |  30 ++++---
 gdb/inferior.c       |   3 -
 gdb/inferior.h       |   6 +-
 gdb/mi/mi-cmd-env.c  |  13 ++-
 gdb/solib.c          |   7 +-
 8 files changed, 181 insertions(+), 176 deletions(-)

diff --git a/gdb/charset.c b/gdb/charset.c
index f55e482..236e918 100644
--- a/gdb/charset.c
+++ b/gdb/charset.c
@@ -794,16 +794,14 @@ find_charset_names (void)
   int err, status;
   int fail = 1;
   int flags;
-  struct gdb_environ *iconv_env;
+  gdb_environ iconv_env;
   char *iconv_program;
 
   /* Older iconvs, e.g. 2.2.2, don't omit the intro text if stdout is
      not a tty.  We need to recognize it and ignore it.  This text is
      subject to translation, so force LANGUAGE=C.  */
-  iconv_env = make_environ ();
-  init_environ (iconv_env);
-  set_in_environ (iconv_env, "LANGUAGE", "C");
-  set_in_environ (iconv_env, "LC_ALL", "C");
+  iconv_env.set ("LANGUAGE", "C");
+  iconv_env.set ("LC_ALL", "C");
 
   child = pex_init (PEX_USE_PIPES, "iconv", NULL);
 
@@ -827,7 +825,7 @@ find_charset_names (void)
   /* Note that we simply ignore errors here.  */
   if (!pex_run_in_environment (child, flags,
 			       args[0], const_cast<char **> (args),
-			       environ_vector (iconv_env),
+			       iconv_env.get_char_vector (),
 			       NULL, NULL, &err))
     {
       FILE *in = pex_read_output (child, 0);
@@ -901,7 +899,6 @@ find_charset_names (void)
 
   xfree (iconv_program);
   pex_free (child);
-  free_environ (iconv_env);
 
   if (fail)
     {
diff --git a/gdb/common/environ.c b/gdb/common/environ.c
index 3145d01..131835e 100644
--- a/gdb/common/environ.c
+++ b/gdb/common/environ.c
@@ -18,165 +18,156 @@
 #include "common-defs.h"
 #include "environ.h"
 #include <algorithm>
-\f
+#include <utility>
 
-/* Return a new environment object.  */
+/* See common/environ.h.  */
 
-struct gdb_environ *
-make_environ (void)
+void
+gdb_environ::init ()
 {
-  struct gdb_environ *e;
+  extern char **environ;
+
+  if (environ == NULL)
+    return;
 
-  e = XNEW (struct gdb_environ);
+  for (int i = 0; environ[i] != NULL; ++i)
+    {
+      std::string v = std::string (environ[i]);
+      std::size_t pos = v.find ('=');
+      std::string var = v.substr (0, pos);
+      std::string value = v.substr (pos + 1);
 
-  e->allocated = 10;
-  e->vector = (char **) xmalloc ((e->allocated + 1) * sizeof (char *));
-  e->vector[0] = 0;
-  return e;
+      this->m_environ_map.insert (std::make_pair (var, value));
+      this->m_environ_vector.push_back (xstrdup (environ[i]));
+    }
+
+  /* The last element of the vector is always going to be NULL.  */
+  this->m_environ_vector.push_back (NULL);
 }
 
-/* Free an environment and all the strings in it.  */
+/* See common/environ.h.  */
 
 void
-free_environ (struct gdb_environ *e)
+gdb_environ::clear ()
 {
-  char **vector = e->vector;
+  this->m_environ_map.clear ();
+  for (char *v : this->m_environ_vector)
+    xfree (v);
+  this->m_environ_vector.clear ();
+}
 
-  while (*vector)
-    xfree (*vector++);
+/* See common/environ.h.  */
 
-  xfree (e->vector);
-  xfree (e);
+gdb_environ::gdb_environ ()
+{
+  this->init ();
 }
 
-/* Copy the environment given to this process into E.
-   Also copies all the strings in it, so we can be sure
-   that all strings in these environments are safe to free.  */
+/* See common/environ.h.  */
 
-void
-init_environ (struct gdb_environ *e)
+gdb_environ::~gdb_environ ()
 {
-  extern char **environ;
-  int i;
+  this->clear ();
+}
 
-  if (environ == NULL)
-    return;
+/* See common/environ.h.  */
+
+void
+gdb_environ::reinit ()
+{
+  this->clear ();
+  this->init ();
+}
 
-  for (i = 0; environ[i]; i++) /*EMPTY */ ;
+/* See common/environ.h.  */
 
-  if (e->allocated < i)
+const char *
+gdb_environ::get (const std::string &var) const
+{
+  try
     {
-      e->allocated = std::max (i, e->allocated + 10);
-      e->vector = (char **) xrealloc ((char *) e->vector,
-				      (e->allocated + 1) * sizeof (char *));
+      return (char *) this->m_environ_map.at (var).c_str ();
     }
-
-  memcpy (e->vector, environ, (i + 1) * sizeof (char *));
-
-  while (--i >= 0)
+  catch (const std::out_of_range &ex)
     {
-      int len = strlen (e->vector[i]);
-      char *newobj = (char *) xmalloc (len + 1);
-
-      memcpy (newobj, e->vector[i], len + 1);
-      e->vector[i] = newobj;
+      return NULL;
     }
 }
 
-/* Return the vector of environment E.
-   This is used to get something to pass to execve.  */
+/* Unset (delete) the environment variable VAR on the environment
+   vector V.  */
 
-char **
-environ_vector (struct gdb_environ *e)
+static void
+unset_in_vector (std::vector<char *> &v, const std::string &var)
 {
-  return e->vector;
+  std::string match = var + '=';
+  const char *match_str = match.c_str ();
+
+  for (std::vector<char *>::const_iterator el = v.cbegin ();
+       el != v.cend ();
+       ++el)
+    if (startswith (*el, match_str))
+      {
+	xfree (*el);
+	v.erase (el);
+	break;
+      }
 }
-\f
-/* Return the value in environment E of variable VAR.  */
 
-char *
-get_in_environ (const struct gdb_environ *e, const char *var)
-{
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
-
-  for (; (s = *vector) != NULL; vector++)
-    if (strncmp (s, var, len) == 0 && s[len] == '=')
-      return &s[len + 1];
-
-  return 0;
-}
-
-/* Store the value in E of VAR as VALUE.  */
+/* See common/environ.h.  */
 
 void
-set_in_environ (struct gdb_environ *e, const char *var, const char *value)
+gdb_environ::set (const std::string &var, const std::string &value)
 {
-  int i;
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
+  std::unordered_map<std::string, std::string>::iterator el;
 
-  for (i = 0; (s = vector[i]) != NULL; i++)
-    if (strncmp (s, var, len) == 0 && s[len] == '=')
-      break;
+  el = this->m_environ_map.find (var);
 
-  if (s == 0)
+  if (el != this->m_environ_map.end ())
     {
-      if (i == e->allocated)
+      if (el->second == value)
+	return;
+      else
 	{
-	  e->allocated += 10;
-	  vector = (char **) xrealloc ((char *) vector,
-				       (e->allocated + 1) * sizeof (char *));
-	  e->vector = vector;
+	  /* If we found this item, it means that we have to update
+	     its value on the map.  */
+	  el->second = value;
+	  /* And we also have to update its value on the
+	     environ_vector.  For that, we just delete the item here
+	     and recreate it (with the new value) later.  */
+	  unset_in_vector (this->m_environ_vector, var);
 	}
-      vector[i + 1] = 0;
     }
   else
-    xfree (s);
-
-  s = (char *) xmalloc (len + strlen (value) + 2);
-  strcpy (s, var);
-  strcat (s, "=");
-  strcat (s, value);
-  vector[i] = s;
-
-  /* This used to handle setting the PATH and GNUTARGET variables
-     specially.  The latter has been replaced by "set gnutarget"
-     (which has worked since GDB 4.11).  The former affects searching
-     the PATH to find SHELL, and searching the PATH to find the
-     argument of "symbol-file" or "exec-file".  Maybe we should have
-     some kind of "set exec-path" for that.  But in any event, having
-     "set env" affect anything besides the inferior is a bad idea.
-     What if we want to change the environment we pass to the program
-     without afecting GDB's behavior?  */
-
-  return;
+    this->m_environ_map.insert (std::make_pair (var, value));
+
+  this->m_environ_vector.insert (this->m_environ_vector.end () - 1,
+				 xstrdup (std::string (var
+						       + '='
+						       + value).c_str ()));
 }
 
-/* Remove the setting for variable VAR from environment E.  */
+/* See common/environ.h.  */
 
 void
-unset_in_environ (struct gdb_environ *e, const char *var)
+gdb_environ::unset (const std::string &var)
 {
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
+  this->m_environ_map.erase (var);
+  unset_in_vector (this->m_environ_vector, var);
+}
 
-  for (; (s = *vector) != NULL; vector++)
-    {
-      if (strncmp (s, var, len) == 0 && s[len] == '=')
-	{
-	  xfree (s);
-	  /* Walk through the vector, shuffling args down by one, including
-	     the NULL terminator.  Can't use memcpy() here since the regions
-	     overlap, and memmove() might not be available.  */
-	  while ((vector[0] = vector[1]) != NULL)
-	    {
-	      vector++;
-	    }
-	  break;
-	}
-    }
+/* See common/environ.h.  */
+
+char **
+gdb_environ::get_char_vector () const
+{
+  return const_cast<char **> (&m_environ_vector[0]);
+}
+
+/* See common/environ.h.  */
+
+const std::vector<char *> &
+gdb_environ::get_vector () const
+{
+  return this->m_environ_vector;
 }
diff --git a/gdb/common/environ.h b/gdb/common/environ.h
index 3ace69e..df5e381 100644
--- a/gdb/common/environ.h
+++ b/gdb/common/environ.h
@@ -17,33 +17,57 @@
 #if !defined (ENVIRON_H)
 #define ENVIRON_H 1
 
-/* We manipulate environments represented as these structures.  */
+#include <unordered_map>
+#include <vector>
 
-struct gdb_environ
-  {
-    /* Number of usable slots allocated in VECTOR.
-       VECTOR always has one slot not counted here,
-       to hold the terminating zero.  */
-    int allocated;
-    /* A vector of slots, ALLOCATED + 1 of them.
-       The first few slots contain strings "VAR=VALUE"
-       and the next one contains zero.
-       Then come some unused slots.  */
-    char **vector;
-  };
+/* Class that represents the environment variables as seen by the
+   inferior.  */
 
-extern struct gdb_environ *make_environ (void);
+class gdb_environ
+{
+public:
+  /* Regular constructor and destructor.  */
+  gdb_environ ();
+  ~gdb_environ ();
 
-extern void free_environ (struct gdb_environ *);
+  /* Reinitialize the environment stored.  This is used when the user
+     wants to delete all environment variables added by him/her.  */
+  void reinit ();
 
-extern void init_environ (struct gdb_environ *);
+  /* Return the value in the environment for the variable VAR.  The
+     return pointer is only valid as long as VAR is not
+     removed/replaced from the environment.  */
+  const char *get (const std::string &var) const;
 
-extern char *get_in_environ (const struct gdb_environ *, const char *);
+  /* Store VAR=VALUE in the environment.  */
+  void set (const std::string &var, const std::string &value);
 
-extern void set_in_environ (struct gdb_environ *, const char *, const char *);
+  /* Unset VAR in environment.  */
+  void unset (const std::string &var);
 
-extern void unset_in_environ (struct gdb_environ *, const char *);
+  /* Return the environment vector represented as a 'char **'.  */
+  char **get_char_vector () const;
 
-extern char **environ_vector (struct gdb_environ *);
+  /* Return the environment vector.  */
+  const std::vector<char *> &get_vector () const;
+
+private:
+  /* Helper function that initializes our data structures with the
+     environment variables.  */
+  void init ();
+
+  /* Helper function that clears our data structures.  */
+  void clear ();
+
+  /* A map representing the environment variables and their values.
+     It is easier to store them using a map because of the faster
+     lookups.  */
+  std::unordered_map<std::string, std::string> m_environ_map;
+
+  /* A vector containing the environment variables.  This is useful
+     for when we need to obtain a 'char **' with all the existing
+     variables.  */
+  std::vector<char *> m_environ_vector;
+};
 
 #endif /* defined (ENVIRON_H) */
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index 22b2c7a..fb27572 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -609,7 +609,8 @@ run_command_1 (char *args, int from_tty, int tbreak_at_main)
      the value now.  */
   run_target->to_create_inferior (run_target, exec_file,
 				  std::string (get_inferior_args ()),
-				  environ_vector (current_inferior ()->environment),
+				  current_inferior ()
+				  ->environment.get_char_vector (),
 				  from_tty);
   /* to_create_inferior should push the target, so after this point we
      shouldn't refer to run_target again.  */
@@ -2141,7 +2142,7 @@ environment_info (char *var, int from_tty)
 {
   if (var)
     {
-      char *val = get_in_environ (current_inferior ()->environment, var);
+      const char *val = current_inferior ()->environment.get (var);
 
       if (val)
 	{
@@ -2159,11 +2160,12 @@ environment_info (char *var, int from_tty)
     }
   else
     {
-      char **vector = environ_vector (current_inferior ()->environment);
+      const std::vector<char *> &vector =
+	current_inferior ()->environment.get_vector ();
 
-      while (*vector)
+      for (char *elem : vector)
 	{
-	  puts_filtered (*vector++);
+	  puts_filtered (elem);
 	  puts_filtered ("\n");
 	}
     }
@@ -2225,10 +2227,10 @@ set_environment_command (char *arg, int from_tty)
       printf_filtered (_("Setting environment variable "
 			 "\"%s\" to null value.\n"),
 		       var);
-      set_in_environ (current_inferior ()->environment, var, "");
+      current_inferior ()->environment.set (var, "");
     }
   else
-    set_in_environ (current_inferior ()->environment, var, val);
+    current_inferior ()->environment.set (var, val);
   xfree (var);
 }
 
@@ -2240,13 +2242,10 @@ unset_environment_command (char *var, int from_tty)
       /* If there is no argument, delete all environment variables.
          Ask for confirmation if reading from the terminal.  */
       if (!from_tty || query (_("Delete all environment variables? ")))
-	{
-	  free_environ (current_inferior ()->environment);
-	  current_inferior ()->environment = make_environ ();
-	}
+	current_inferior ()->environment.reinit ();
     }
   else
-    unset_in_environ (current_inferior ()->environment, var);
+    current_inferior ()->environment.unset (var);
 }
 
 /* Handle the execution path (PATH variable).  */
@@ -2257,8 +2256,7 @@ static void
 path_info (char *args, int from_tty)
 {
   puts_filtered ("Executable and object file path: ");
-  puts_filtered (get_in_environ (current_inferior ()->environment,
-				 path_var_name));
+  puts_filtered (current_inferior ()->environment.get (path_var_name));
   puts_filtered ("\n");
 }
 
@@ -2271,13 +2269,13 @@ path_command (char *dirname, int from_tty)
   const char *env;
 
   dont_repeat ();
-  env = get_in_environ (current_inferior ()->environment, path_var_name);
+  env = current_inferior ()->environment.get (path_var_name);
   /* Can be null if path is not set.  */
   if (!env)
     env = "";
   exec_path = xstrdup (env);
   mod_path (dirname, &exec_path);
-  set_in_environ (current_inferior ()->environment, path_var_name, exec_path);
+  current_inferior ()->environment.set (path_var_name, exec_path);
   xfree (exec_path);
   if (from_tty)
     path_info ((char *) NULL, from_tty);
diff --git a/gdb/inferior.c b/gdb/inferior.c
index 327590a..b0cdaf6 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -100,7 +100,6 @@ inferior::~inferior ()
   inferior_free_data (inf);
   xfree (inf->args);
   xfree (inf->terminal);
-  free_environ (inf->environment);
   target_desc_info_free (inf->tdesc_info);
   xfree (inf->priv);
 }
@@ -108,10 +107,8 @@ inferior::~inferior ()
 inferior::inferior (int pid_)
   : num (++highest_inferior_num),
     pid (pid_),
-    environment (make_environ ()),
     registry_data ()
 {
-  init_environ (this->environment);
   inferior_alloc_data (this);
 }
 
diff --git a/gdb/inferior.h b/gdb/inferior.h
index b5eb3d1..dd74d4d 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -30,7 +30,6 @@ struct regcache;
 struct ui_out;
 struct terminal_info;
 struct target_desc_info;
-struct gdb_environ;
 struct continuation;
 
 /* For bpstat.  */
@@ -42,6 +41,9 @@ struct continuation;
 /* For struct frame_id.  */
 #include "frame.h"
 
+/* For gdb_environ.  */
+#include "environ.h"
+
 #include "progspace.h"
 #include "registry.h"
 
@@ -362,7 +364,7 @@ public:
 
   /* Environment to use for running inferior,
      in format described in environ.h.  */
-  gdb_environ *environment = NULL;
+  gdb_environ environment;
 
   /* True if this child process was attached rather than forked.  */
   bool attach_flag = false;
diff --git a/gdb/mi/mi-cmd-env.c b/gdb/mi/mi-cmd-env.c
index 4093178..37c0745 100644
--- a/gdb/mi/mi-cmd-env.c
+++ b/gdb/mi/mi-cmd-env.c
@@ -167,7 +167,7 @@ mi_cmd_env_path (const char *command, char **argv, int argc)
   else
     {
       /* Otherwise, get current path to modify.  */
-      env = get_in_environ (current_inferior ()->environment, path_var_name);
+      env = current_inferior ()->environment.get (path_var_name);
 
       /* Can be null if path is not set.  */
       if (!env)
@@ -178,9 +178,9 @@ mi_cmd_env_path (const char *command, char **argv, int argc)
   for (i = argc - 1; i >= 0; --i)
     env_mod_path (argv[i], &exec_path);
 
-  set_in_environ (current_inferior ()->environment, path_var_name, exec_path);
+  current_inferior ()->environment.set (path_var_name, exec_path);
   xfree (exec_path);
-  env = get_in_environ (current_inferior ()->environment, path_var_name);
+  env = current_inferior ()->environment.get (path_var_name);
   uiout->field_string ("path", env);
 }
 
@@ -270,7 +270,7 @@ mi_cmd_inferior_tty_show (const char *command, char **argv, int argc)
 void 
 _initialize_mi_cmd_env (void)
 {
-  struct gdb_environ *environment;
+  gdb_environ environment;
   const char *env;
 
   /* We want original execution path to reset to, if desired later.
@@ -278,13 +278,10 @@ _initialize_mi_cmd_env (void)
      current_inferior ()->environment.  Also, there's no obvious
      place where this code can be moved such that it surely run
      before any code possibly mangles original PATH.  */
-  environment = make_environ ();
-  init_environ (environment);
-  env = get_in_environ (environment, path_var_name);
+  env = environment.get (path_var_name);
 
   /* Can be null if path is not set.  */
   if (!env)
     env = "";
   orig_path = xstrdup (env);
-  free_environ (environment);
 }
diff --git a/gdb/solib.c b/gdb/solib.c
index af94383..27eb451 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -350,16 +350,15 @@ solib_find_1 (const char *in_pathname, int *fd, int is_solib)
 
   /* If not found, next search the inferior's $PATH environment variable.  */
   if (found_file < 0 && sysroot == NULL)
-    found_file = openp (get_in_environ (current_inferior ()->environment,
-					"PATH"),
+    found_file = openp (current_inferior ()->environment.get ("PATH"),
 			OPF_TRY_CWD_FIRST | OPF_RETURN_REALPATH, in_pathname,
 			O_RDONLY | O_BINARY, &temp_pathname);
 
   /* If not found, and we're looking for a solib, next search the
      inferior's $LD_LIBRARY_PATH environment variable.  */
   if (is_solib && found_file < 0 && sysroot == NULL)
-    found_file = openp (get_in_environ (current_inferior ()->environment,
-					"LD_LIBRARY_PATH"),
+    found_file = openp (current_inferior ()->environment.get
+			("LD_LIBRARY_PATH"),
 			OPF_TRY_CWD_FIRST | OPF_RETURN_REALPATH, in_pathname,
 			O_RDONLY | O_BINARY, &temp_pathname);
 
-- 
2.9.3

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v3] C++ify gdb/common/environ.c
  2017-04-18  3:03 ` [PATCH v3] " Sergio Durigan Junior
@ 2017-04-19  4:56   ` Simon Marchi
  2017-04-19 16:30     ` Pedro Alves
  2017-04-19 18:14   ` Pedro Alves
  1 sibling, 1 reply; 47+ messages in thread
From: Simon Marchi @ 2017-04-19  4:56 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: GDB Patches

On 2017-04-17 23:03, Sergio Durigan Junior wrote:
> Changes from v2 (mostly from Simon's review):
> 
> - Not using std::unique_ptr for gdb_environ anymore.
> 
> - Got rid of "_environ" suffixes from the names of the methods.
> 
> - Remove (void) from methods without parameters.
> 
> - Constify return of 'get' method.
> 
> - Change 'const char *' for 'const std::string &' on methods'
>   parameters.
> 
> - Returning a 'const std::vector<char *> &' on the 'get_vector'
>   method.
> 
> - Typos, and other minor nits.
> 
> 
> 
> Disclaimer: this patch depends on
> <https://sourceware.org/ml/gdb-patches/2017-03/msg00551.html> to be
> fully testable.
> 
> As part of the preparation necessary for my upcoming task, I'd like to
> propose that we turn gdb_environ into a class.  The approach taken
> here is simple: the class gdb_environ contains everything that is
> needed to manipulate the environment variables.  These variables are
> stored in two data structures: an unordered_set, good because lookups
> are O(n), and an std::vector<char *>, which can be converted to a
> 'char **' and passed as argument to functions that need it.

It still says O(n) ;)

> diff --git a/gdb/common/environ.c b/gdb/common/environ.c
> index 3145d01..131835e 100644
> --- a/gdb/common/environ.c
> +++ b/gdb/common/environ.c
> @@ -18,165 +18,156 @@
>  #include "common-defs.h"
>  #include "environ.h"
>  #include <algorithm>
> -\f
> +#include <utility>
> 
> -/* Return a new environment object.  */
> +/* See common/environ.h.  */
> 
> -struct gdb_environ *
> -make_environ (void)
> +void
> +gdb_environ::init ()
>  {
> -  struct gdb_environ *e;
> +  extern char **environ;
> +
> +  if (environ == NULL)
> +    return;
> 
> -  e = XNEW (struct gdb_environ);
> +  for (int i = 0; environ[i] != NULL; ++i)
> +    {
> +      std::string v = std::string (environ[i]);
> +      std::size_t pos = v.find ('=');
> +      std::string var = v.substr (0, pos);
> +      std::string value = v.substr (pos + 1);
> 
> -  e->allocated = 10;
> -  e->vector = (char **) xmalloc ((e->allocated + 1) * sizeof (char 
> *));
> -  e->vector[0] = 0;
> -  return e;
> +      this->m_environ_map.insert (std::make_pair (var, value));

I see that make_pair has a move/xvalue version:

   template< class T1, class T2 >
   std::pair<V1,V2> make_pair( T1&& t, T2&& u );

Does that mean that we could/should do

   this->m_environ_map.insert (std::make_pair (std::move (var), std::move 
(value)));

in order to reuse the resources of the existing strings instead of 
copying them?

> +const char *
> +gdb_environ::get (const std::string &var) const
> +{
> +  try
>      {
> -      e->allocated = std::max (i, e->allocated + 10);
> -      e->vector = (char **) xrealloc ((char *) e->vector,
> -				      (e->allocated + 1) * sizeof (char *));
> +      return (char *) this->m_environ_map.at (var).c_str ();

You can remove the cast here.

I didn't think about it at first, but some unit tests for this class 
would be nice as well.

Thanks,

Simon

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v3] C++ify gdb/common/environ.c
  2017-04-19  4:56   ` Simon Marchi
@ 2017-04-19 16:30     ` Pedro Alves
  0 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2017-04-19 16:30 UTC (permalink / raw)
  To: Simon Marchi, Sergio Durigan Junior; +Cc: GDB Patches

On 04/19/2017 05:56 AM, Simon Marchi wrote:
>>
>> +      return (char *) this->m_environ_map.at (var).c_str ();
> 
> You can remove the cast here.

And while at it, please write "m_environ_map" without the leading 
"this->".

(Likewise other similar cases, I haven't looked at the patch in
any detail.)

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v3] C++ify gdb/common/environ.c
  2017-04-18  3:03 ` [PATCH v3] " Sergio Durigan Junior
  2017-04-19  4:56   ` Simon Marchi
@ 2017-04-19 18:14   ` Pedro Alves
  2017-05-01  2:22     ` Sergio Durigan Junior
  1 sibling, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2017-04-19 18:14 UTC (permalink / raw)
  To: Sergio Durigan Junior, GDB Patches; +Cc: Simon Marchi

On 04/18/2017 04:03 AM, Sergio Durigan Junior wrote:

> -  if (e->allocated < i)
> +const char *
> +gdb_environ::get (const std::string &var) const
> +{
> +  try
>      {
> -      e->allocated = std::max (i, e->allocated + 10);
> -      e->vector = (char **) xrealloc ((char *) e->vector,
> -				      (e->allocated + 1) * sizeof (char *));
> +      return (char *) this->m_environ_map.at (var).c_str ();
>      }
> -
> -  memcpy (e->vector, environ, (i + 1) * sizeof (char *));
> -
> -  while (--i >= 0)
> +  catch (const std::out_of_range &ex)

Please use unordered_map::find instead.  In general,
use "at" with exceptions when you're "sure" the element
exists.  It'll be simpler, more efficient, and less
roundabout, since at() essentially does a find() and then
throws if not found.

>      {
> -      int len = strlen (e->vector[i]);
> -      char *newobj = (char *) xmalloc (len + 1);
> -
> -      memcpy (newobj, e->vector[i], len + 1);
> -      e->vector[i] = newobj;
> +      return NULL;
>      }
>  }
>  

>  void
> -set_in_environ (struct gdb_environ *e, const char *var, const char *value)
> +gdb_environ::set (const std::string &var, const std::string &value)
>  {
> -  int i;
> -  int len = strlen (var);
> -  char **vector = e->vector;
> -  char *s;
> +  std::unordered_map<std::string, std::string>::iterator el;
>  
> -  for (i = 0; (s = vector[i]) != NULL; i++)
> -    if (strncmp (s, var, len) == 0 && s[len] == '=')
> -      break;
> +  el = this->m_environ_map.find (var);

In C++, it's a good practice to avoid first initializing,
and then assigning separately, because the default constructor
will then do useless work.  (And in some cases, there may not even
be a default constructor.)

I'm not a fan or "auto" everywhere, but with iterators,
I find it OK:

   auto el = this->m_environ_map.find (var);

>  
> -  if (s == 0)
> +  if (el != this->m_environ_map.end ())
>      {
> -      if (i == e->allocated)
> +      if (el->second == value)
> +	return;
> +      else
>  	{
> -	  e->allocated += 10;
> -	  vector = (char **) xrealloc ((char *) vector,
> -				       (e->allocated + 1) * sizeof (char *));
> -	  e->vector = vector;
> +	  /* If we found this item, it means that we have to update
> +	     its value on the map.  */
> +	  el->second = value;
> +	  /* And we also have to update its value on the
> +	     environ_vector.  For that, we just delete the item here
> +	     and recreate it (with the new value) later.  */
> +	  unset_in_vector (this->m_environ_vector, var);
>  	}
> -      vector[i + 1] = 0;
>      }
>    else
> -    xfree (s);
> -
> -  s = (char *) xmalloc (len + strlen (value) + 2);
> -  strcpy (s, var);
> -  strcat (s, "=");
> -  strcat (s, value);
> -  vector[i] = s;
> -
> -  /* This used to handle setting the PATH and GNUTARGET variables
> -     specially.  The latter has been replaced by "set gnutarget"
> -     (which has worked since GDB 4.11).  The former affects searching
> -     the PATH to find SHELL, and searching the PATH to find the
> -     argument of "symbol-file" or "exec-file".  Maybe we should have
> -     some kind of "set exec-path" for that.  But in any event, having
> -     "set env" affect anything besides the inferior is a bad idea.
> -     What if we want to change the environment we pass to the program
> -     without afecting GDB's behavior?  */
> -
> -  return;
> +    this->m_environ_map.insert (std::make_pair (var, value));
> +
> +  this->m_environ_vector.insert (this->m_environ_vector.end () - 1,
> +				 xstrdup (std::string (var
> +						       + '='
> +						       + value).c_str ()));

I don't really understand the "m_environ_vector.end () - 1" here.

Also, that's a lot of unnecessary copying/duping in that last
statement.  Using concat instead of xstrdup + operator+ would
easily avoid it.

And you could easily avoid having to dup the string into both the
map and the vector by making the map's value be a const char *
instead of a std::string:

  std::unordered_map<std::string, const char *>

and then make a map's value point directly into the value
part of the string that is owned by the vector (as in,
the substring that starts at VALUE in "VAR=VALUE").

>  }
>  
> -/* Remove the setting for variable VAR from environment E.  */
> +/* See common/environ.h.  */
>  
>  void
> -unset_in_environ (struct gdb_environ *e, const char *var)
> +gdb_environ::unset (const std::string &var)
>  {
> -  int len = strlen (var);
> -  char **vector = e->vector;
> -  char *s;
> +  this->m_environ_map.erase (var);
> +  unset_in_vector (this->m_environ_vector, var);
> +}


> -extern struct gdb_environ *make_environ (void);
> +class gdb_environ
> +{
> +public:
> +  /* Regular constructor and destructor.  */
> +  gdb_environ ();
> +  ~gdb_environ ();
>  
> -extern void free_environ (struct gdb_environ *);
> +  /* Reinitialize the environment stored.  This is used when the user
> +     wants to delete all environment variables added by him/her.  */
> +  void reinit ();

This should mention that the initial state is copied from the host's
environ.   I think the default ctor could use a similar comment.
I was going to suggest to call this clear() instead, to go
with the standard containers' terminology, until I realized what
"init" really does.  Or maybe even find a more explicit name,
reset_from_host_environ or some such.

Perhaps an even clearer approach would be to make the default ctor
not do a deep copy of the host's "environ", but instead add a
static factor method that returned such a new gdb_environ, like:

static gdb_environ
gdb_environ::from_host_environ ()
{
   // build/return a gdb_environ that wraps the host's environ global.
}

Not sure.  Only experimenting would tell.

Speaking of copying the host environ:

>  _initialize_mi_cmd_env (void)
>  {
> -  struct gdb_environ *environment;
> +  gdb_environ environment;
>    const char *env;
>  
>    /* We want original execution path to reset to, if desired later.
> @@ -278,13 +278,10 @@ _initialize_mi_cmd_env (void)
>       current_inferior ()->environment.  Also, there's no obvious
>       place where this code can be moved such that it surely run
>       before any code possibly mangles original PATH.  */
> -  environment = make_environ ();
> -  init_environ (environment);
> -  env = get_in_environ (environment, path_var_name);
> +  env = environment.get (path_var_name);
>  
>    /* Can be null if path is not set.  */
>    if (!env)
>      env = "";
>    orig_path = xstrdup (env);
> -  free_environ (environment);
>  }

This usage of gdb_environ looks like pointless
wrapping / dupping / freeing to me -- I don't see why we
need to dup the whole host environment just to get at some
env variable.  Using good old getenv(3) directly should do,
and end up trimming off a bit of work from gdb's startup.

I could see perhaps wanting to avoid / optimize the linear
walks that getenv must do, if we have many getenv calls in
gdb, but then that suggests keeping a gdb_environ global that
is initialized early on and is reused.  But that would miss
any setenv call that changes the environment since that
gdb_environ is created, so I'd prefer not do that unless
we find a real need.

> +
> +private:
> +  /* Helper function that initializes our data structures with the
> +     environment variables.  */
> +  void init ();
> +
> +  /* Helper function that clears our data structures.  */
> +  void clear ();

s/function/method/g throughout (ChangeLog too).

> +
> +  /* A map representing the environment variables and their values.
> +     It is easier to store them using a map because of the faster
> +     lookups.  */

It's not that it's easier to store them.  Not having a map
would be even easier.  Just do a linear walk on the vector,
just like getenv does.  You want to instead say that we're
optimizing for get operations.  (I'm not sure this is the
right trade off for this class, but I'll go along with it.)

> +  std::unordered_map<std::string, std::string> m_environ_map;
> +
> +  /* A vector containing the environment variables.  This is useful
> +     for when we need to obtain a 'char **' with all the existing
> +     variables.  */

This should say that the entries are in VAR=VALUE form, and what
is the typical user that needs that format.

> +  std::vector<char *> m_environ_vector;
> +};
>  
>  #endif /* defined (ENVIRON_H) */

>    else
>      {
> -      char **vector = environ_vector (current_inferior ()->environment);
> +      const std::vector<char *> &vector =
> +	current_inferior ()->environment.get_vector ();

= goes on the next line.

>  
> -      while (*vector)
> +      for (char *elem : vector)
>  	{
> -	  puts_filtered (*vector++);
> +	  puts_filtered (elem);
>  	  puts_filtered ("\n");
>  	}
>      }

I'm not sure it's worth it to expose the std::vector implementation
detail just for this loop.  I don't really understand how this
can work though, given that the vector is NULL terminated.

I agree with Simon -- this is begging for some unit tests.  :-)

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v3] C++ify gdb/common/environ.c
  2017-04-19 18:14   ` Pedro Alves
@ 2017-05-01  2:22     ` Sergio Durigan Junior
  2017-05-04 15:30       ` Pedro Alves
  0 siblings, 1 reply; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-05-01  2:22 UTC (permalink / raw)
  To: Pedro Alves; +Cc: GDB Patches, Simon Marchi

Hey,

Thanks for the review, and really sorry about the delay.  I have a few
comments and questions below.

On Wednesday, April 19 2017, Pedro Alves wrote:

> On 04/18/2017 04:03 AM, Sergio Durigan Junior wrote:
>
>> -  if (e->allocated < i)
>> +const char *
>> +gdb_environ::get (const std::string &var) const
>> +{
>> +  try
>>      {
>> -      e->allocated = std::max (i, e->allocated + 10);
>> -      e->vector = (char **) xrealloc ((char *) e->vector,
>> -				      (e->allocated + 1) * sizeof (char *));
>> +      return (char *) this->m_environ_map.at (var).c_str ();
>>      }
>> -
>> -  memcpy (e->vector, environ, (i + 1) * sizeof (char *));
>> -
>> -  while (--i >= 0)
>> +  catch (const std::out_of_range &ex)
>
> Please use unordered_map::find instead.  In general,
> use "at" with exceptions when you're "sure" the element
> exists.  It'll be simpler, more efficient, and less
> roundabout, since at() essentially does a find() and then
> throws if not found.

I decided to go the easier way and not use unordered_map.  I confess I
was having second thoughts myself when I posted the patch, and your
message only made me more confident that I don't want to follow this
route.

>>      {
>> -      int len = strlen (e->vector[i]);
>> -      char *newobj = (char *) xmalloc (len + 1);
>> -
>> -      memcpy (newobj, e->vector[i], len + 1);
>> -      e->vector[i] = newobj;
>> +      return NULL;
>>      }
>>  }
>>  
>
>>  void
>> -set_in_environ (struct gdb_environ *e, const char *var, const char *value)
>> +gdb_environ::set (const std::string &var, const std::string &value)
>>  {
>> -  int i;
>> -  int len = strlen (var);
>> -  char **vector = e->vector;
>> -  char *s;
>> +  std::unordered_map<std::string, std::string>::iterator el;
>>  
>> -  for (i = 0; (s = vector[i]) != NULL; i++)
>> -    if (strncmp (s, var, len) == 0 && s[len] == '=')
>> -      break;
>> +  el = this->m_environ_map.find (var);
>
> In C++, it's a good practice to avoid first initializing,
> and then assigning separately, because the default constructor
> will then do useless work.  (And in some cases, there may not even
> be a default constructor.)
>
> I'm not a fan or "auto" everywhere, but with iterators,
> I find it OK:
>
>    auto el = this->m_environ_map.find (var);

Without the unordered_map, this is no longer a problem.  But of course,
thanks for the review.

>>  
>> -  if (s == 0)
>> +  if (el != this->m_environ_map.end ())
>>      {
>> -      if (i == e->allocated)
>> +      if (el->second == value)
>> +	return;
>> +      else
>>  	{
>> -	  e->allocated += 10;
>> -	  vector = (char **) xrealloc ((char *) vector,
>> -				       (e->allocated + 1) * sizeof (char *));
>> -	  e->vector = vector;
>> +	  /* If we found this item, it means that we have to update
>> +	     its value on the map.  */
>> +	  el->second = value;
>> +	  /* And we also have to update its value on the
>> +	     environ_vector.  For that, we just delete the item here
>> +	     and recreate it (with the new value) later.  */
>> +	  unset_in_vector (this->m_environ_vector, var);
>>  	}
>> -      vector[i + 1] = 0;
>>      }
>>    else
>> -    xfree (s);
>> -
>> -  s = (char *) xmalloc (len + strlen (value) + 2);
>> -  strcpy (s, var);
>> -  strcat (s, "=");
>> -  strcat (s, value);
>> -  vector[i] = s;
>> -
>> -  /* This used to handle setting the PATH and GNUTARGET variables
>> -     specially.  The latter has been replaced by "set gnutarget"
>> -     (which has worked since GDB 4.11).  The former affects searching
>> -     the PATH to find SHELL, and searching the PATH to find the
>> -     argument of "symbol-file" or "exec-file".  Maybe we should have
>> -     some kind of "set exec-path" for that.  But in any event, having
>> -     "set env" affect anything besides the inferior is a bad idea.
>> -     What if we want to change the environment we pass to the program
>> -     without afecting GDB's behavior?  */
>> -
>> -  return;
>> +    this->m_environ_map.insert (std::make_pair (var, value));
>> +
>> +  this->m_environ_vector.insert (this->m_environ_vector.end () - 1,
>> +				 xstrdup (std::string (var
>> +						       + '='
>> +						       + value).c_str ()));
>
> I don't really understand the "m_environ_vector.end () - 1" here.

This is needed because the last element of m_environ_vector needs to be
always NULL.  Therefore, this code is basically inserting the new
variable in the second-to-last position of the vector.  I made a comment
on top of it to clarify this part.

Also, there are other places where I need to iterate through the
elements of the vector, and I'm also using the "- 1" in these places.
I'll put comments where applicable.

> Also, that's a lot of unnecessary copying/duping in that last
> statement.  Using concat instead of xstrdup + operator+ would
> easily avoid it.

You're right, I've replaced it by a concat and it's simpler now.

> And you could easily avoid having to dup the string into both the
> map and the vector by making the map's value be a const char *
> instead of a std::string:
>
>   std::unordered_map<std::string, const char *>
>
> and then make a map's value point directly into the value
> part of the string that is owned by the vector (as in,
> the substring that starts at VALUE in "VAR=VALUE").

That was a good idea.

>>  }
>>  
>> -/* Remove the setting for variable VAR from environment E.  */
>> +/* See common/environ.h.  */
>>  
>>  void
>> -unset_in_environ (struct gdb_environ *e, const char *var)
>> +gdb_environ::unset (const std::string &var)
>>  {
>> -  int len = strlen (var);
>> -  char **vector = e->vector;
>> -  char *s;
>> +  this->m_environ_map.erase (var);
>> +  unset_in_vector (this->m_environ_vector, var);
>> +}
>
>
>> -extern struct gdb_environ *make_environ (void);
>> +class gdb_environ
>> +{
>> +public:
>> +  /* Regular constructor and destructor.  */
>> +  gdb_environ ();
>> +  ~gdb_environ ();
>>  
>> -extern void free_environ (struct gdb_environ *);
>> +  /* Reinitialize the environment stored.  This is used when the user
>> +     wants to delete all environment variables added by him/her.  */
>> +  void reinit ();
>
> This should mention that the initial state is copied from the host's
> environ.   I think the default ctor could use a similar comment.
> I was going to suggest to call this clear() instead, to go
> with the standard containers' terminology, until I realized what
> "init" really does.  Or maybe even find a more explicit name,
> reset_from_host_environ or some such.

Sorry, I guess I wasn't aware of the importance of specifying that the
variables come from the host.  Somehow I thought this was implied.

I guess reset_from_host_environ is a good name for the method; my other
option would be "reinit_using_host_environ", but that's longer.

> Perhaps an even clearer approach would be to make the default ctor
> not do a deep copy of the host's "environ", but instead add a
> static factor method that returned such a new gdb_environ, like:
>
> static gdb_environ
> gdb_environ::from_host_environ ()
> {
>    // build/return a gdb_environ that wraps the host's environ global.
> }
>
> Not sure.  Only experimenting would tell.

Sorry, I'm not sure I understand your suggestion.  Please correct me if
I'm wrong.

You're basically saying that the default ctor shouldn't actually do
anything; instead, the class should have this new from_host_environ
method which would be the "official" ctor.  The users would then call
from_host_environ directly when they wanted an instance of the class,
and the method would be responsible for initializing the object.  Is
that correct?

If yes, I think I fail to see the advantage of this method over having a
normal ctor (aside from explicitly naming the new ctor after the fact
that we're using the host environ to build the object).

... after some reading ...

Oh, I think I see what you're suggesting.  Instead of building one
gdb_environ every time someone requests it, we build just one and pass
it along.  OK, now it makes sense.

> Speaking of copying the host environ:
>
>>  _initialize_mi_cmd_env (void)
>>  {
>> -  struct gdb_environ *environment;
>> +  gdb_environ environment;
>>    const char *env;
>>  
>>    /* We want original execution path to reset to, if desired later.
>> @@ -278,13 +278,10 @@ _initialize_mi_cmd_env (void)
>>       current_inferior ()->environment.  Also, there's no obvious
>>       place where this code can be moved such that it surely run
>>       before any code possibly mangles original PATH.  */
>> -  environment = make_environ ();
>> -  init_environ (environment);
>> -  env = get_in_environ (environment, path_var_name);
>> +  env = environment.get (path_var_name);
>>  
>>    /* Can be null if path is not set.  */
>>    if (!env)
>>      env = "";
>>    orig_path = xstrdup (env);
>> -  free_environ (environment);
>>  }
>
> This usage of gdb_environ looks like pointless
> wrapping / dupping / freeing to me -- I don't see why we
> need to dup the whole host environment just to get at some
> env variable.  Using good old getenv(3) directly should do,
> and end up trimming off a bit of work from gdb's startup.

Absolutely.  I didn't pay close attention to this bit; I was just
mechanically converting the code.

> I could see perhaps wanting to avoid / optimize the linear
> walks that getenv must do, if we have many getenv calls in
> gdb, but then that suggests keeping a gdb_environ global that
> is initialized early on and is reused.  But that would miss
> any setenv call that changes the environment since that
> gdb_environ is created, so I'd prefer not do that unless
> we find a real need.

I'll make the code use getenv instead.

>> +
>> +private:
>> +  /* Helper function that initializes our data structures with the
>> +     environment variables.  */
>> +  void init ();
>> +
>> +  /* Helper function that clears our data structures.  */
>> +  void clear ();
>
> s/function/method/g throughout (ChangeLog too).

Ops, thanks.  Fixed.

>> +
>> +  /* A map representing the environment variables and their values.
>> +     It is easier to store them using a map because of the faster
>> +     lookups.  */
>
> It's not that it's easier to store them.  Not having a map
> would be even easier.  Just do a linear walk on the vector,
> just like getenv does.  You want to instead say that we're
> optimizing for get operations.  (I'm not sure this is the
> right trade off for this class, but I'll go along with it.)

Well, no need to worry about this anymore.  The new code doesn't use
unordered_map, as mentioned above.

>> +  std::unordered_map<std::string, std::string> m_environ_map;
>> +
>> +  /* A vector containing the environment variables.  This is useful
>> +     for when we need to obtain a 'char **' with all the existing
>> +     variables.  */
>
> This should say that the entries are in VAR=VALUE form, and what
> is the typical user that needs that format.

I'll expand the comment.

>> +  std::vector<char *> m_environ_vector;
>> +};
>>  
>>  #endif /* defined (ENVIRON_H) */
>
>>    else
>>      {
>> -      char **vector = environ_vector (current_inferior ()->environment);
>> +      const std::vector<char *> &vector =
>> +	current_inferior ()->environment.get_vector ();
>
> = goes on the next line.

Fixed.

>>  
>> -      while (*vector)
>> +      for (char *elem : vector)
>>  	{
>> -	  puts_filtered (*vector++);
>> +	  puts_filtered (elem);
>>  	  puts_filtered ("\n");
>>  	}
>>      }
>
> I'm not sure it's worth it to expose the std::vector implementation
> detail just for this loop.  I don't really understand how this
> can work though, given that the vector is NULL terminated.

I'm not exposing the std::vector anymore.  Instead, I'm just using the
regular 'char **' and looping until I find a NULL.

> I agree with Simon -- this is begging for some unit tests.  :-)

I'm writing them.  It's taking some time because it's the first time I'm
using the unittest framework.

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v3] C++ify gdb/common/environ.c
  2017-05-01  2:22     ` Sergio Durigan Junior
@ 2017-05-04 15:30       ` Pedro Alves
  0 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2017-05-04 15:30 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: GDB Patches, Simon Marchi

On 05/01/2017 03:22 AM, Sergio Durigan Junior wrote:

>>> +  this->m_environ_vector.insert (this->m_environ_vector.end () - 1,
>>> +				 xstrdup (std::string (var
>>> +						       + '='
>>> +						       + value).c_str ()));
>>
>> I don't really understand the "m_environ_vector.end () - 1" here.
> 
> This is needed because the last element of m_environ_vector needs to be
> always NULL.  Therefore, this code is basically inserting the new
> variable in the second-to-last position of the vector.  I made a comment
> on top of it to clarify this part.

Ah, OK.

> 
> Also, there are other places where I need to iterate through the
> elements of the vector, and I'm also using the "- 1" in these places.
> I'll put comments where applicable.

OK, I'll take another look when you post it.

>>> -extern struct gdb_environ *make_environ (void);
>>> +class gdb_environ
>>> +{
>>> +public:
>>> +  /* Regular constructor and destructor.  */
>>> +  gdb_environ ();
>>> +  ~gdb_environ ();
>>>  
>>> -extern void free_environ (struct gdb_environ *);
>>> +  /* Reinitialize the environment stored.  This is used when the user
>>> +     wants to delete all environment variables added by him/her.  */
>>> +  void reinit ();
>>
>> This should mention that the initial state is copied from the host's
>> environ.   I think the default ctor could use a similar comment.
>> I was going to suggest to call this clear() instead, to go
>> with the standard containers' terminology, until I realized what
>> "init" really does.  Or maybe even find a more explicit name,
>> reset_from_host_environ or some such.
> 
> Sorry, I guess I wasn't aware of the importance of specifying that the
> variables come from the host.  Somehow I thought this was implied.
> 
> I guess reset_from_host_environ is a good name for the method; my other
> option would be "reinit_using_host_environ", but that's longer.
> 
>> Perhaps an even clearer approach would be to make the default ctor
>> not do a deep copy of the host's "environ", but instead add a
>> static factor method that returned such a new gdb_environ, like:
>>
>> static gdb_environ
>> gdb_environ::from_host_environ ()
>> {
>>    // build/return a gdb_environ that wraps the host's environ global.
>> }
>>
>> Not sure.  Only experimenting would tell.
> 
> Sorry, I'm not sure I understand your suggestion.  Please correct me if
> I'm wrong.
> 
> You're basically saying that the default ctor shouldn't actually do
> anything; instead, the class should have this new from_host_environ
> method which would be the "official" ctor.  The users would then call
> from_host_environ directly when they wanted an instance of the class,
> and the method would be responsible for initializing the object.  Is
> that correct?
> 
> If yes, I think I fail to see the advantage of this method over having a
> normal ctor (aside from explicitly naming the new ctor after the fact
> that we're using the host environ to build the object).

The advantage was all in that "aside" -- to make the code
document itself.  It was totally non-obvious to me at first
that:

  gdb_environ env;

makes a copy of the host's environment.  I just assumed it created
an empty environment.  Probably because in my mind I don't think
it'll make sense for a remote process to inherit the host's
environment variables.

> 
> ... after some reading ...
> 
> Oh, I think I see what you're suggesting.  Instead of building one
> gdb_environ every time someone requests it, we build just one and pass
> it along.  OK, now it makes sense.

That wasn't actually what I was saying.  :-)

> 
>> Speaking of copying the host environ:
>>
>>>  _initialize_mi_cmd_env (void)
>>>  {
>>> -  struct gdb_environ *environment;
>>> +  gdb_environ environment;
>>>    const char *env;
>>>  
>>>    /* We want original execution path to reset to, if desired later.
>>> @@ -278,13 +278,10 @@ _initialize_mi_cmd_env (void)
>>>       current_inferior ()->environment.  Also, there's no obvious
>>>       place where this code can be moved such that it surely run
>>>       before any code possibly mangles original PATH.  */
>>> -  environment = make_environ ();
>>> -  init_environ (environment);
>>> -  env = get_in_environ (environment, path_var_name);
>>> +  env = environment.get (path_var_name);
>>>  
>>>    /* Can be null if path is not set.  */
>>>    if (!env)
>>>      env = "";
>>>    orig_path = xstrdup (env);
>>> -  free_environ (environment);
>>>  }
>>
>> This usage of gdb_environ looks like pointless
>> wrapping / dupping / freeing to me -- I don't see why we
>> need to dup the whole host environment just to get at some
>> env variable.  Using good old getenv(3) directly should do,
>> and end up trimming off a bit of work from gdb's startup.
> 
> Absolutely.  I didn't pay close attention to this bit; I was just
> mechanically converting the code.
> 
>> I could see perhaps wanting to avoid / optimize the linear
>> walks that getenv must do, if we have many getenv calls in
>> gdb, but then that suggests keeping a gdb_environ global that
>> is initialized early on and is reused.  But that would miss
>> any setenv call that changes the environment since that
>> gdb_environ is created, so I'd prefer not do that unless
>> we find a real need.
> 
> I'll make the code use getenv instead.

Thanks.  That bit could/should go in as its own
preparatory/obvious patch first, no need to carry it in
the same patch.

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 47+ messages in thread

* [PATCH v4] C++ify gdb/common/environ.c
  2017-04-13  4:05 [PATCH] C++ify gdb/common/environ.c Sergio Durigan Junior
  2017-04-15 18:51 ` [PATCH v2] " Sergio Durigan Junior
  2017-04-18  3:03 ` [PATCH v3] " Sergio Durigan Junior
@ 2017-06-14 19:22 ` Sergio Durigan Junior
  2017-06-16 15:45   ` Pedro Alves
  2017-06-16 22:23 ` [PATCH v5] " Sergio Durigan Junior
                   ` (3 subsequent siblings)
  6 siblings, 1 reply; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-06-14 19:22 UTC (permalink / raw)
  To: GDB Patches; +Cc: Simon Marchi, Pedro Alves, Sergio Durigan Junior

As part of the preparation necessary for my upcoming task, I'd like to
propose that we turn gdb_environ into a class.  The approach taken
here is simple: the class gdb_environ contains everything that is
needed to manipulate the environment variables.  These variables are
stored in an std::vector<char *>, which can be converted to a 'char
**' and passed as argument to functions that need it.

The usage has not changed much.  As per Pedro's suggestion, this class
uses a static factory method initialization.  This means that when an
instance is created, it is initially empty.  When needed, it has to be
initialized using the static method 'from_host_environ'.

As mentioned before, this is a preparation for an upcoming work that I
will be posting in the next few weeks or so.  For that work, I'll
probably create another data structure that will contain all the
environment variables that were set by the user using the 'set
environment' command, because I'll need access to them.  This will be
much easier with the class-ification of gdb_environ.

As noted, this has been regression-tested with the new version of
environ.exp and no regressions were found.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	* Makefile.in (SUBDIR_UNITTESTS_SRCS): Add
	'unittests/environ-selftests.c'.
	(SUBDIR_UNITTESTS_OBS): Add 'environ-selftests.o'.
	* charset.c (find_charset_names): Declare object 'iconv_env'.
	Update code to use 'iconv_env' object.  Remove call to
	'free_environ'.
	* common/environ.c: Include <utility>.
	(make_environ): Delete function.
	(free_environ): Delete function.
	(gdb_environ::clear): New function.
	(gdb_environ::gdb_environ): New function.
	(gdb_environ::~gdb_environ): New function.
	(gdb_environ::operator=): New function.
	(gdb_environ::get): Likewise.
	(environ_vector): Delete function.
	(set_in_environ): Delete function.
	(gdb_environ::set): New function.
	(unset_in_environ): Delete function.
	(gdb_environ::unset): New function.
	(gdb_environ::get_char_vector): Likewise.
	* common/environ.h: Include <vector>.
	(struct gdb_environ): Delete; transform into...
	(class gdb_environ): ... this class.
	(free_environ): Delete prototype.
	(init_environ, get_in_environ, set_in_environ, unset_in_environ,
	environ_vector): Likewise.
	* infcmd.c (run_command_1): Update code to call
	'get_char_vector' from 'gdb_environ' class.
	(environment_info): Update code to call methods from 'gdb_environ'
	class.
	(unset_environment_command): Likewise.
	(path_info): Likewise.
	(path_command): Likewise.
	* inferior.c (inferior::~inferior): Delete call to 'free_environ'.
	(inferior::~inferior): Initialize 'environment' using the host's
	information.
	* inferior.h: Remove forward declaration of 'struct gdb_environ'.
	Include "environ.h".
	(class inferior) <environment>: Change type from 'struct
	gdb_environ' to 'gdb_environ'.
	* mi/mi-cmd-env.c (mi_cmd_env_path): Update code to call
	methods from 'gdb_environ' class.
	(mi_cmd_inferior_tty_show): Use 'getenv' instead of 'gdb_environ'.
	(_initialize_mi_cmd_env): Update code to call methods from
	'gdb_environ' class.
	* solib.c (solib_find_1): Likewise
	* unittests/environ-selftests.c: New file.

gdb/gdbserver/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	* linux-low.c (linux_create_inferior): Adjust code to access the
	environment information via 'gdb_environ' class.
	* lynx-low.c (lynx_create_inferior): Likewise.
	* server.c (our_environ): Make it an instance of 'gdb_environ'.
	(get_environ): Return a pointer to 'our_environ'.
	(captured_main): Initialize 'our_environ'.
	* server.h (get_environ): Adjust prototype.
	* spu-low.c (spu_create_inferior): Adjust code to access the
	environment information via 'gdb_environ' class.
---
 gdb/Makefile.in                   |   6 +-
 gdb/charset.c                     |  11 +--
 gdb/common/environ.c              | 191 +++++++++++++-------------------------
 gdb/common/environ.h              |  70 ++++++++++----
 gdb/gdbserver/linux-low.c         |   2 +-
 gdb/gdbserver/lynx-low.c          |   2 +-
 gdb/gdbserver/server.c            |   9 +-
 gdb/gdbserver/server.h            |   6 +-
 gdb/gdbserver/spu-low.c           |   2 +-
 gdb/infcmd.c                      |  37 ++++----
 gdb/inferior.c                    |   4 +-
 gdb/inferior.h                    |   6 +-
 gdb/mi/mi-cmd-env.c               |  12 +--
 gdb/solib.c                       |   7 +-
 gdb/unittests/environ-selftests.c |  79 ++++++++++++++++
 15 files changed, 242 insertions(+), 202 deletions(-)
 create mode 100644 gdb/unittests/environ-selftests.c

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 5e5fcaa..133db1a 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -530,14 +530,16 @@ SUBDIR_UNITTESTS_SRCS = \
 	unittests/offset-type-selftests.c \
 	unittests/optional-selftests.c \
 	unittests/ptid-selftests.c \
-	unittests/scoped_restore-selftests.c
+	unittests/scoped_restore-selftests.c \
+	unittests/environ-selftests.c
 
 SUBDIR_UNITTESTS_OBS = \
 	function-view-selftests.o \
 	offset-type-selftests.o \
 	optional-selftests.o \
 	ptid-selftests.o \
-	scoped_restore-selftests.o
+	scoped_restore-selftests.o \
+	environ-selftests.o
 
 # Opcodes currently live in one of two places.  Either they are in the
 # opcode library, typically ../opcodes, or they are in a header file
diff --git a/gdb/charset.c b/gdb/charset.c
index dbe46a4..51ebeaa 100644
--- a/gdb/charset.c
+++ b/gdb/charset.c
@@ -794,16 +794,14 @@ find_charset_names (void)
   int err, status;
   int fail = 1;
   int flags;
-  struct gdb_environ *iconv_env;
+  gdb_environ iconv_env = gdb_environ::from_host_environ ();
   char *iconv_program;
 
   /* Older iconvs, e.g. 2.2.2, don't omit the intro text if stdout is
      not a tty.  We need to recognize it and ignore it.  This text is
      subject to translation, so force LANGUAGE=C.  */
-  iconv_env = make_environ ();
-  init_environ (iconv_env);
-  set_in_environ (iconv_env, "LANGUAGE", "C");
-  set_in_environ (iconv_env, "LC_ALL", "C");
+  iconv_env.set ("LANGUAGE", "C");
+  iconv_env.set ("LC_ALL", "C");
 
   child = pex_init (PEX_USE_PIPES, "iconv", NULL);
 
@@ -827,7 +825,7 @@ find_charset_names (void)
   /* Note that we simply ignore errors here.  */
   if (!pex_run_in_environment (child, flags,
 			       args[0], const_cast<char **> (args),
-			       environ_vector (iconv_env),
+			       iconv_env.get_char_vector (),
 			       NULL, NULL, &err))
     {
       FILE *in = pex_read_output (child, 0);
@@ -901,7 +899,6 @@ find_charset_names (void)
 
   xfree (iconv_program);
   pex_free (child);
-  free_environ (iconv_env);
 
   if (fail)
     {
diff --git a/gdb/common/environ.c b/gdb/common/environ.c
index 3145d01..657f2e0 100644
--- a/gdb/common/environ.c
+++ b/gdb/common/environ.c
@@ -18,165 +18,102 @@
 #include "common-defs.h"
 #include "environ.h"
 #include <algorithm>
-\f
+#include <utility>
 
-/* Return a new environment object.  */
+/* See common/environ.h.  */
 
-struct gdb_environ *
-make_environ (void)
+gdb_environ::gdb_environ ()
 {
-  struct gdb_environ *e;
+}
 
-  e = XNEW (struct gdb_environ);
+/* See common/environ.h.  */
 
-  e->allocated = 10;
-  e->vector = (char **) xmalloc ((e->allocated + 1) * sizeof (char *));
-  e->vector[0] = 0;
-  return e;
+gdb_environ::~gdb_environ ()
+{
+  clear ();
 }
 
-/* Free an environment and all the strings in it.  */
+/* See common/environ.h.  */
 
-void
-free_environ (struct gdb_environ *e)
+gdb_environ::gdb_environ (gdb_environ &&e)
+  : m_environ_vector (std::move (e.m_environ_vector))
 {
-  char **vector = e->vector;
+}
 
-  while (*vector)
-    xfree (*vector++);
+/* See common/environ.h.  */
 
-  xfree (e->vector);
-  xfree (e);
+gdb_environ &
+gdb_environ::operator= (gdb_environ &&e)
+{
+  clear ();
+  m_environ_vector = std::move (e.m_environ_vector);
+  return *this;
 }
 
-/* Copy the environment given to this process into E.
-   Also copies all the strings in it, so we can be sure
-   that all strings in these environments are safe to free.  */
+/* See common/environ.h.  */
 
 void
-init_environ (struct gdb_environ *e)
+gdb_environ::clear ()
 {
-  extern char **environ;
-  int i;
-
-  if (environ == NULL)
-    return;
-
-  for (i = 0; environ[i]; i++) /*EMPTY */ ;
+  for (char *v : m_environ_vector)
+    xfree (v);
+  m_environ_vector.clear ();
+}
 
-  if (e->allocated < i)
-    {
-      e->allocated = std::max (i, e->allocated + 10);
-      e->vector = (char **) xrealloc ((char *) e->vector,
-				      (e->allocated + 1) * sizeof (char *));
-    }
+/* See common/environ.h.  */
 
-  memcpy (e->vector, environ, (i + 1) * sizeof (char *));
+const char *
+gdb_environ::get (const std::string &var) const
+{
+  size_t len = var.size ();
+  const char *var_str = var.c_str ();
 
-  while (--i >= 0)
-    {
-      int len = strlen (e->vector[i]);
-      char *newobj = (char *) xmalloc (len + 1);
+  for (char *el : m_environ_vector)
+    if (el != NULL && strncmp (el, var_str, len) == 0 && el[len] == '=')
+      return &el[len + 1];
 
-      memcpy (newobj, e->vector[i], len + 1);
-      e->vector[i] = newobj;
-    }
+  return NULL;
 }
 
-/* Return the vector of environment E.
-   This is used to get something to pass to execve.  */
+/* See common/environ.h.  */
 
-char **
-environ_vector (struct gdb_environ *e)
-{
-  return e->vector;
-}
-\f
-/* Return the value in environment E of variable VAR.  */
-
-char *
-get_in_environ (const struct gdb_environ *e, const char *var)
+void
+gdb_environ::set (const std::string &var, const std::string &value)
 {
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
-
-  for (; (s = *vector) != NULL; vector++)
-    if (strncmp (s, var, len) == 0 && s[len] == '=')
-      return &s[len + 1];
+  /* We have to unset the variable in the vector if it exists.  */
+  unset (var);
 
-  return 0;
+  /* Insert the element before the last one, which is always NULL.  */
+  m_environ_vector.insert (m_environ_vector.end () - 1,
+			   concat (var.c_str (), "=",
+				   value.c_str (), NULL));
 }
 
-/* Store the value in E of VAR as VALUE.  */
+/* See common/environ.h.  */
 
 void
-set_in_environ (struct gdb_environ *e, const char *var, const char *value)
+gdb_environ::unset (const std::string &var)
 {
-  int i;
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
-
-  for (i = 0; (s = vector[i]) != NULL; i++)
-    if (strncmp (s, var, len) == 0 && s[len] == '=')
-      break;
-
-  if (s == 0)
-    {
-      if (i == e->allocated)
-	{
-	  e->allocated += 10;
-	  vector = (char **) xrealloc ((char *) vector,
-				       (e->allocated + 1) * sizeof (char *));
-	  e->vector = vector;
-	}
-      vector[i + 1] = 0;
-    }
-  else
-    xfree (s);
-
-  s = (char *) xmalloc (len + strlen (value) + 2);
-  strcpy (s, var);
-  strcat (s, "=");
-  strcat (s, value);
-  vector[i] = s;
-
-  /* This used to handle setting the PATH and GNUTARGET variables
-     specially.  The latter has been replaced by "set gnutarget"
-     (which has worked since GDB 4.11).  The former affects searching
-     the PATH to find SHELL, and searching the PATH to find the
-     argument of "symbol-file" or "exec-file".  Maybe we should have
-     some kind of "set exec-path" for that.  But in any event, having
-     "set env" affect anything besides the inferior is a bad idea.
-     What if we want to change the environment we pass to the program
-     without afecting GDB's behavior?  */
-
-  return;
+  std::string match = var + '=';
+  const char *match_str = match.c_str ();
+
+  /* We iterate until '.cend () - 1' because the last element is
+     always NULL.  */
+  for (std::vector<char *>::const_iterator el = m_environ_vector.cbegin ();
+       el != m_environ_vector.cend () - 1;
+       ++el)
+    if (startswith (*el, match_str))
+      {
+	xfree (*el);
+	m_environ_vector.erase (el);
+	break;
+      }
 }
 
-/* Remove the setting for variable VAR from environment E.  */
+/* See common/environ.h.  */
 
-void
-unset_in_environ (struct gdb_environ *e, const char *var)
+char **
+gdb_environ::get_char_vector () const
 {
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
-
-  for (; (s = *vector) != NULL; vector++)
-    {
-      if (strncmp (s, var, len) == 0 && s[len] == '=')
-	{
-	  xfree (s);
-	  /* Walk through the vector, shuffling args down by one, including
-	     the NULL terminator.  Can't use memcpy() here since the regions
-	     overlap, and memmove() might not be available.  */
-	  while ((vector[0] = vector[1]) != NULL)
-	    {
-	      vector++;
-	    }
-	  break;
-	}
-    }
+  return const_cast<char **> (&m_environ_vector[0]);
 }
diff --git a/gdb/common/environ.h b/gdb/common/environ.h
index 3ace69e..c3503e1 100644
--- a/gdb/common/environ.h
+++ b/gdb/common/environ.h
@@ -17,33 +17,65 @@
 #if !defined (ENVIRON_H)
 #define ENVIRON_H 1
 
-/* We manipulate environments represented as these structures.  */
+#include <vector>
 
-struct gdb_environ
+/* Class that represents the environment variables as seen by the
+   inferior.  */
+
+class gdb_environ
+{
+public:
+  /* Regular constructor and destructor.  */
+  gdb_environ ();
+  ~gdb_environ ();
+
+  /* Move constructor.  */
+  gdb_environ (gdb_environ &&e);
+
+  /* Move assignment.  */
+  gdb_environ &operator= (gdb_environ &&e);
+
+  /* Create a gdb_environ object using the host's environment
+     variables.  */
+  static gdb_environ from_host_environ ()
   {
-    /* Number of usable slots allocated in VECTOR.
-       VECTOR always has one slot not counted here,
-       to hold the terminating zero.  */
-    int allocated;
-    /* A vector of slots, ALLOCATED + 1 of them.
-       The first few slots contain strings "VAR=VALUE"
-       and the next one contains zero.
-       Then come some unused slots.  */
-    char **vector;
-  };
+    extern char **environ;
+    gdb_environ e;
+
+    if (environ == NULL)
+      return e;
+
+    for (int i = 0; environ[i] != NULL; ++i)
+      e.m_environ_vector.push_back (xstrdup (environ[i]));
+
+    /* The last element of the vector is always going to be NULL.  */
+    e.m_environ_vector.push_back (NULL);
 
-extern struct gdb_environ *make_environ (void);
+    return e;
+  }
 
-extern void free_environ (struct gdb_environ *);
+  /* Clear the environment variables stored in the object.  */
+  void clear ();
 
-extern void init_environ (struct gdb_environ *);
+  /* Return the value in the environment for the variable VAR.  The
+     return pointer is only valid as long as VAR is not
+     removed/replaced from the environment.  */
+  const char *get (const std::string &var) const;
 
-extern char *get_in_environ (const struct gdb_environ *, const char *);
+  /* Store VAR=VALUE in the environment.  */
+  void set (const std::string &var, const std::string &value);
 
-extern void set_in_environ (struct gdb_environ *, const char *, const char *);
+  /* Unset VAR in environment.  */
+  void unset (const std::string &var);
 
-extern void unset_in_environ (struct gdb_environ *, const char *);
+  /* Return the environment vector represented as a 'char **'.  */
+  char **get_char_vector () const;
 
-extern char **environ_vector (struct gdb_environ *);
+private:
+  /* A vector containing the environment variables.  This is useful
+     for when we need to obtain a 'char **' with all the existing
+     variables.  */
+  std::vector<char *> m_environ_vector;
+};
 
 #endif /* defined (ENVIRON_H) */
diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index 7fbf744..33272f0 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -998,7 +998,7 @@ linux_create_inferior (const char *program,
 
   pid = fork_inferior (program,
 		       str_program_args.c_str (),
-		       environ_vector (get_environ ()), linux_ptrace_fun,
+		       get_environ ()->get_char_vector (), linux_ptrace_fun,
 		       NULL, NULL, NULL, NULL);
 
   do_cleanups (restore_personality);
diff --git a/gdb/gdbserver/lynx-low.c b/gdb/gdbserver/lynx-low.c
index 35160d6..ddca54b 100644
--- a/gdb/gdbserver/lynx-low.c
+++ b/gdb/gdbserver/lynx-low.c
@@ -259,7 +259,7 @@ lynx_create_inferior (const char *program,
 
   pid = fork_inferior (program,
 		       str_program_args.c_str (),
-		       environ_vector (get_environ ()), lynx_ptrace_fun,
+		       get_environ ()->get_char_vector (), lynx_ptrace_fun,
 		       NULL, NULL, NULL, NULL);
 
   post_fork_inferior (pid, program);
diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c
index 1d7a8b0..3838351 100644
--- a/gdb/gdbserver/server.c
+++ b/gdb/gdbserver/server.c
@@ -42,7 +42,7 @@
 
 /* The environment to pass to the inferior when creating it.  */
 
-struct gdb_environ *our_environ = NULL;
+static gdb_environ our_environ;
 
 /* Start the inferior using a shell.  */
 
@@ -257,10 +257,10 @@ get_exec_file (int err)
 
 /* See server.h.  */
 
-struct gdb_environ *
+gdb_environ *
 get_environ ()
 {
-  return our_environ;
+  return &our_environ;
 }
 
 static int
@@ -3698,8 +3698,7 @@ captured_main (int argc, char *argv[])
     }
 
   /* Gather information about the environment.  */
-  our_environ = make_environ ();
-  init_environ (our_environ);
+  our_environ = gdb_environ::from_host_environ ();
 
   initialize_async_io ();
   initialize_low ();
diff --git a/gdb/gdbserver/server.h b/gdb/gdbserver/server.h
index 4de4244..46b614c 100644
--- a/gdb/gdbserver/server.h
+++ b/gdb/gdbserver/server.h
@@ -62,6 +62,7 @@ int vsnprintf(char *str, size_t size, const char *format, va_list ap);
 #include "mem-break.h"
 #include "gdbthread.h"
 #include "inferiors.h"
+#include "environ.h"
 
 /* Target-specific functions */
 
@@ -154,9 +155,8 @@ extern int in_queued_stop_replies (ptid_t ptid);
    inferior and PROGRAM is its name.  */
 extern void post_fork_inferior (int pid, const char *program);
 
-/* Get the 'struct gdb_environ *' being used in the current
-   session.  */
-extern struct gdb_environ *get_environ ();
+/* Get the gdb_environ being used in the current session.  */
+extern gdb_environ *get_environ ();
 
 extern target_waitstatus last_status;
 extern ptid_t last_ptid;
diff --git a/gdb/gdbserver/spu-low.c b/gdb/gdbserver/spu-low.c
index 0f770a0..d54b121 100644
--- a/gdb/gdbserver/spu-low.c
+++ b/gdb/gdbserver/spu-low.c
@@ -289,7 +289,7 @@ spu_create_inferior (const char *program,
 
   pid = fork_inferior (program,
 		       str_program_args.c_str (),
-		       environ_vector (get_environ ()), spu_ptrace_fun,
+		       get_environ ()->get_char_vector (), spu_ptrace_fun,
 		       NULL, NULL, NULL, NULL);
 
   post_fork_inferior (pid, program);
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index db09f19..ab46c1a 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -610,7 +610,8 @@ run_command_1 (char *args, int from_tty, int tbreak_at_main)
      the value now.  */
   run_target->to_create_inferior (run_target, exec_file,
 				  std::string (get_inferior_args ()),
-				  environ_vector (current_inferior ()->environment),
+				  current_inferior ()
+				  ->environment.get_char_vector (),
 				  from_tty);
   /* to_create_inferior should push the target, so after this point we
      shouldn't refer to run_target again.  */
@@ -2131,7 +2132,7 @@ environment_info (char *var, int from_tty)
 {
   if (var)
     {
-      char *val = get_in_environ (current_inferior ()->environment, var);
+      const char *val = current_inferior ()->environment.get (var);
 
       if (val)
 	{
@@ -2149,13 +2150,15 @@ environment_info (char *var, int from_tty)
     }
   else
     {
-      char **vector = environ_vector (current_inferior ()->environment);
+      char **env_vec
+	= current_inferior ()->environment.get_char_vector ();
 
-      while (*vector)
-	{
-	  puts_filtered (*vector++);
-	  puts_filtered ("\n");
-	}
+      if (env_vec != NULL)
+	for (int idx = 0; env_vec[idx] != NULL; ++idx)
+	  {
+	    puts_filtered (env_vec[idx]);
+	    puts_filtered ("\n");
+	  }
     }
 }
 
@@ -2215,10 +2218,10 @@ set_environment_command (char *arg, int from_tty)
       printf_filtered (_("Setting environment variable "
 			 "\"%s\" to null value.\n"),
 		       var);
-      set_in_environ (current_inferior ()->environment, var, "");
+      current_inferior ()->environment.set (var, "");
     }
   else
-    set_in_environ (current_inferior ()->environment, var, val);
+    current_inferior ()->environment.set (var, val);
   xfree (var);
 }
 
@@ -2230,13 +2233,10 @@ unset_environment_command (char *var, int from_tty)
       /* If there is no argument, delete all environment variables.
          Ask for confirmation if reading from the terminal.  */
       if (!from_tty || query (_("Delete all environment variables? ")))
-	{
-	  free_environ (current_inferior ()->environment);
-	  current_inferior ()->environment = make_environ ();
-	}
+	current_inferior ()->environment = gdb_environ::from_host_environ ();
     }
   else
-    unset_in_environ (current_inferior ()->environment, var);
+    current_inferior ()->environment.unset (var);
 }
 
 /* Handle the execution path (PATH variable).  */
@@ -2247,8 +2247,7 @@ static void
 path_info (char *args, int from_tty)
 {
   puts_filtered ("Executable and object file path: ");
-  puts_filtered (get_in_environ (current_inferior ()->environment,
-				 path_var_name));
+  puts_filtered (current_inferior ()->environment.get (path_var_name));
   puts_filtered ("\n");
 }
 
@@ -2261,13 +2260,13 @@ path_command (char *dirname, int from_tty)
   const char *env;
 
   dont_repeat ();
-  env = get_in_environ (current_inferior ()->environment, path_var_name);
+  env = current_inferior ()->environment.get (path_var_name);
   /* Can be null if path is not set.  */
   if (!env)
     env = "";
   exec_path = xstrdup (env);
   mod_path (dirname, &exec_path);
-  set_in_environ (current_inferior ()->environment, path_var_name, exec_path);
+  current_inferior ()->environment.set (path_var_name, exec_path);
   xfree (exec_path);
   if (from_tty)
     path_info ((char *) NULL, from_tty);
diff --git a/gdb/inferior.c b/gdb/inferior.c
index 0b655f4..9fa2dad 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -81,7 +81,6 @@ inferior::~inferior ()
   inferior_free_data (inf);
   xfree (inf->args);
   xfree (inf->terminal);
-  free_environ (inf->environment);
   target_desc_info_free (inf->tdesc_info);
   xfree (inf->priv);
 }
@@ -89,10 +88,9 @@ inferior::~inferior ()
 inferior::inferior (int pid_)
   : num (++highest_inferior_num),
     pid (pid_),
-    environment (make_environ ()),
+    environment (gdb_environ::from_host_environ ()),
     registry_data ()
 {
-  init_environ (this->environment);
   inferior_alloc_data (this);
 }
 
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 1c541b7..8ada4f8 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -30,7 +30,6 @@ struct regcache;
 struct ui_out;
 struct terminal_info;
 struct target_desc_info;
-struct gdb_environ;
 struct continuation;
 struct inferior;
 
@@ -43,6 +42,9 @@ struct inferior;
 /* For struct frame_id.  */
 #include "frame.h"
 
+/* For gdb_environ.  */
+#include "environ.h"
+
 #include "progspace.h"
 #include "registry.h"
 
@@ -363,7 +365,7 @@ public:
 
   /* Environment to use for running inferior,
      in format described in environ.h.  */
-  gdb_environ *environment = NULL;
+  gdb_environ environment;
 
   /* True if this child process was attached rather than forked.  */
   bool attach_flag = false;
diff --git a/gdb/mi/mi-cmd-env.c b/gdb/mi/mi-cmd-env.c
index 4093178..a185096 100644
--- a/gdb/mi/mi-cmd-env.c
+++ b/gdb/mi/mi-cmd-env.c
@@ -167,7 +167,7 @@ mi_cmd_env_path (const char *command, char **argv, int argc)
   else
     {
       /* Otherwise, get current path to modify.  */
-      env = get_in_environ (current_inferior ()->environment, path_var_name);
+      env = current_inferior ()->environment.get (path_var_name);
 
       /* Can be null if path is not set.  */
       if (!env)
@@ -178,9 +178,9 @@ mi_cmd_env_path (const char *command, char **argv, int argc)
   for (i = argc - 1; i >= 0; --i)
     env_mod_path (argv[i], &exec_path);
 
-  set_in_environ (current_inferior ()->environment, path_var_name, exec_path);
+  current_inferior ()->environment.set (path_var_name, exec_path);
   xfree (exec_path);
-  env = get_in_environ (current_inferior ()->environment, path_var_name);
+  env = current_inferior ()->environment.get (path_var_name);
   uiout->field_string ("path", env);
 }
 
@@ -270,7 +270,6 @@ mi_cmd_inferior_tty_show (const char *command, char **argv, int argc)
 void 
 _initialize_mi_cmd_env (void)
 {
-  struct gdb_environ *environment;
   const char *env;
 
   /* We want original execution path to reset to, if desired later.
@@ -278,13 +277,10 @@ _initialize_mi_cmd_env (void)
      current_inferior ()->environment.  Also, there's no obvious
      place where this code can be moved such that it surely run
      before any code possibly mangles original PATH.  */
-  environment = make_environ ();
-  init_environ (environment);
-  env = get_in_environ (environment, path_var_name);
+  env = getenv (path_var_name);
 
   /* Can be null if path is not set.  */
   if (!env)
     env = "";
   orig_path = xstrdup (env);
-  free_environ (environment);
 }
diff --git a/gdb/solib.c b/gdb/solib.c
index 491c18a..788cf15 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -350,16 +350,15 @@ solib_find_1 (const char *in_pathname, int *fd, int is_solib)
 
   /* If not found, next search the inferior's $PATH environment variable.  */
   if (found_file < 0 && sysroot == NULL)
-    found_file = openp (get_in_environ (current_inferior ()->environment,
-					"PATH"),
+    found_file = openp (current_inferior ()->environment.get ("PATH"),
 			OPF_TRY_CWD_FIRST | OPF_RETURN_REALPATH, in_pathname,
 			O_RDONLY | O_BINARY, &temp_pathname);
 
   /* If not found, and we're looking for a solib, next search the
      inferior's $LD_LIBRARY_PATH environment variable.  */
   if (is_solib && found_file < 0 && sysroot == NULL)
-    found_file = openp (get_in_environ (current_inferior ()->environment,
-					"LD_LIBRARY_PATH"),
+    found_file = openp (current_inferior ()->environment.get
+			("LD_LIBRARY_PATH"),
 			OPF_TRY_CWD_FIRST | OPF_RETURN_REALPATH, in_pathname,
 			O_RDONLY | O_BINARY, &temp_pathname);
 
diff --git a/gdb/unittests/environ-selftests.c b/gdb/unittests/environ-selftests.c
new file mode 100644
index 0000000..948eacf
--- /dev/null
+++ b/gdb/unittests/environ-selftests.c
@@ -0,0 +1,79 @@
+/* Self tests for gdb_environ for GDB, the GNU debugger.
+
+   Copyright (C) 2017 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "selftest.h"
+#include "common/environ.h"
+
+namespace selftests {
+namespace environ {
+
+static void
+run_tests ()
+{
+  if (setenv ("GDB_SELFTEST_ENVIRON", "1", 1) != 0)
+    error ("Could not set environment variable for testing.");
+
+  gdb_environ env;
+
+  SELF_CHECK (env.get ("PWD") == NULL);
+
+  env = gdb_environ::from_host_environ ();
+
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0);
+
+  env.set ("GDB_SELFTEST_ENVIRON", "test");
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "test") == 0);
+
+  env.unset ("GDB_SELFTEST_ENVIRON");
+  SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL);
+
+  env.set ("GDB_SELFTEST_ENVIRON", "1");
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0);
+
+  env.clear ();
+  SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL);
+
+  if (setenv ("GDB_SELFTEST_ENVIRON", "1", 1) != 0)
+    error ("Could not set environment variable for testing.");
+
+  env = gdb_environ::from_host_environ ();
+  char **penv = env.get_char_vector ();
+  bool found_var = false, found_twice = false;
+
+  for (size_t i = 0; penv[i] != NULL; ++i)
+    if (strcmp (penv[i], "GDB_SELFTEST_ENVIRON=1") == 0)
+      {
+	if (found_var)
+	  found_twice = true;
+	found_var = true;
+      }
+  SELF_CHECK (found_var == true);
+  SELF_CHECK (found_twice == false);
+
+  unsetenv ("GDB_SELFTEST_ENVIRON");
+}
+} /* namespace environ */
+} /* namespace selftests */
+
+void
+_initialize_environ_selftests ()
+{
+  register_self_test (selftests::environ::run_tests);
+}
-- 
2.9.3

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v4] C++ify gdb/common/environ.c
  2017-06-14 19:22 ` [PATCH v4] " Sergio Durigan Junior
@ 2017-06-16 15:45   ` Pedro Alves
  2017-06-16 18:01     ` Sergio Durigan Junior
  0 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2017-06-16 15:45 UTC (permalink / raw)
  To: Sergio Durigan Junior, GDB Patches; +Cc: Simon Marchi

On 06/14/2017 08:22 PM, Sergio Durigan Junior wrote:

> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
> index 5e5fcaa..133db1a 100644
> --- a/gdb/Makefile.in
> +++ b/gdb/Makefile.in
> @@ -530,14 +530,16 @@ SUBDIR_UNITTESTS_SRCS = \
>  	unittests/offset-type-selftests.c \
>  	unittests/optional-selftests.c \
>  	unittests/ptid-selftests.c \
> -	unittests/scoped_restore-selftests.c
> +	unittests/scoped_restore-selftests.c \
> +	unittests/environ-selftests.c

Please keep the list sorted.

(I'm guilty of missing that before too.)

>  
>  SUBDIR_UNITTESTS_OBS = \
>  	function-view-selftests.o \
>  	offset-type-selftests.o \
>  	optional-selftests.o \
>  	ptid-selftests.o \
> -	scoped_restore-selftests.o
> +	scoped_restore-selftests.o \
> +	environ-selftests.o

Ditto.

> diff --git a/gdb/common/environ.c b/gdb/common/environ.c
> index 3145d01..657f2e0 100644
> --- a/gdb/common/environ.c
> +++ b/gdb/common/environ.c
> @@ -18,165 +18,102 @@
>  #include "common-defs.h"
>  #include "environ.h"
>  #include <algorithm>
> -\f
> +#include <utility>
>  
> -/* Return a new environment object.  */
> +/* See common/environ.h.  */
>  
> -struct gdb_environ *
> -make_environ (void)
> +gdb_environ::gdb_environ ()
>  {
> -  struct gdb_environ *e;
> +}
>  
> -  e = XNEW (struct gdb_environ);
> +/* See common/environ.h.  */
>  
> -  e->allocated = 10;
> -  e->vector = (char **) xmalloc ((e->allocated + 1) * sizeof (char *));
> -  e->vector[0] = 0;
> -  return e;
> +gdb_environ::~gdb_environ ()
> +{
> +  clear ();
>  }
>  
> -/* Free an environment and all the strings in it.  */
> +/* See common/environ.h.  */
>  
> -void
> -free_environ (struct gdb_environ *e)
> +gdb_environ::gdb_environ (gdb_environ &&e)
> +  : m_environ_vector (std::move (e.m_environ_vector))
>  {
> -  char **vector = e->vector;
> +}
>  
> -  while (*vector)
> -    xfree (*vector++);
> +/* See common/environ.h.  */
>  
> -  xfree (e->vector);
> -  xfree (e);
> +gdb_environ &
> +gdb_environ::operator= (gdb_environ &&e)
> +{
> +  clear ();
> +  m_environ_vector = std::move (e.m_environ_vector);
> +  return *this;
>  }
>  
> -/* Copy the environment given to this process into E.
> -   Also copies all the strings in it, so we can be sure
> -   that all strings in these environments are safe to free.  */
> +/* See common/environ.h.  */
>  
>  void
> -init_environ (struct gdb_environ *e)
> +gdb_environ::clear ()
>  {
> -  extern char **environ;
> -  int i;
> -
> -  if (environ == NULL)
> -    return;
> -
> -  for (i = 0; environ[i]; i++) /*EMPTY */ ;
> +  for (char *v : m_environ_vector)
> +    xfree (v);
> +  m_environ_vector.clear ();
> +}
>  
> -  if (e->allocated < i)
> -    {
> -      e->allocated = std::max (i, e->allocated + 10);
> -      e->vector = (char **) xrealloc ((char *) e->vector,
> -				      (e->allocated + 1) * sizeof (char *));
> -    }
> +/* See common/environ.h.  */
>  
> -  memcpy (e->vector, environ, (i + 1) * sizeof (char *));
> +const char *
> +gdb_environ::get (const std::string &var) const
> +{

Does this need to be a std::string instead of "const char *"?
Callers always pass a string literal down, so this is going to
force a deep string dup for no good reason, AFAICS.


> +  size_t len = var.size ();
> +  const char *var_str = var.c_str ();
>  
> -  while (--i >= 0)
> -    {
> -      int len = strlen (e->vector[i]);
> -      char *newobj = (char *) xmalloc (len + 1);
> +  for (char *el : m_environ_vector)
> +    if (el != NULL && strncmp (el, var_str, len) == 0 && el[len] == '=')
> +      return &el[len + 1];
>  
> -      memcpy (newobj, e->vector[i], len + 1);
> -      e->vector[i] = newobj;
> -    }
> +  return NULL;
>  }

> -char *
> -get_in_environ (const struct gdb_environ *e, const char *var)
> +void
> +gdb_environ::set (const std::string &var, const std::string &value)

Ditto.

>  {
> -  int len = strlen (var);
> -  char **vector = e->vector;
> -  char *s;
> -
> -  for (; (s = *vector) != NULL; vector++)
> -    if (strncmp (s, var, len) == 0 && s[len] == '=')
> -      return &s[len + 1];
> +  /* We have to unset the variable in the vector if it exists.  */
> +  unset (var);
>  
> -  return 0;
> +  /* Insert the element before the last one, which is always NULL.  */
> +  m_environ_vector.insert (m_environ_vector.end () - 1,
> +			   concat (var.c_str (), "=",
> +				   value.c_str (), NULL));
>  }
>  
> -/* Store the value in E of VAR as VALUE.  */
> +/* See common/environ.h.  */
>  
>  void
> -set_in_environ (struct gdb_environ *e, const char *var, const char *value)
> +gdb_environ::unset (const std::string &var)

Ditto.


> -
> -  return;
> +  std::string match = var + '=';
> +  const char *match_str = match.c_str ();
> +
> +  /* We iterate until '.cend () - 1' because the last element is
> +     always NULL.  */
> +  for (std::vector<char *>::const_iterator el = m_environ_vector.cbegin ();
> +       el != m_environ_vector.cend () - 1;
> +       ++el)
> +    if (startswith (*el, match_str))

In gdb_environ::set you used:

    strncmp (el, var_str, len) == 0 && el[len] == '='

It'd be better if places used the same matching code.  Maybe even put
this in a separate helper function.  The gdb_environ::set version
looks better to me for avoiding temporary heap-allocated strings.


> -/* Remove the setting for variable VAR from environment E.  */
> +/* See common/environ.h.  */
>  
> -void
> -unset_in_environ (struct gdb_environ *e, const char *var)
> +char **
> +gdb_environ::get_char_vector () const

So far, getters in gdb's classes don't have a "get_" prefix.
(except "get()" or course, but that's really a getter in
the same sense.)  Can we drop it here?  Like:

 char **gdb_environ::char_vector () const

Though I'd rename it like this instead:

 char ** gdb_environ::envp () const

Because that's what the env vector is traditionally called, e.g.,
from "man 2 execve":

     int execve(const char *filename, char *const argv[],
                  char *const envp[]);

     int main(int argc, char *argv[], char *envp[])

Likewise I'd use that name for local variables where
we call gdb_environ::get_char_vector, just to follow
traditional terminology throughout.

> +/* Class that represents the environment variables as seen by the
> +   inferior.  */
> +
> +class gdb_environ
> +{
> +public:
> +  /* Regular constructor and destructor.  */
> +  gdb_environ ();
> +  ~gdb_environ ();
> +
> +  /* Move constructor.  */
> +  gdb_environ (gdb_environ &&e);
> +
> +  /* Move assignment.  */
> +  gdb_environ &operator= (gdb_environ &&e);
> +
> +  /* Create a gdb_environ object using the host's environment
> +     variables.  */
> +  static gdb_environ from_host_environ ()
>    {

Nit: I find it a bit odd that the ctors/dtors are short but 
defined out of line, while this function is defined inline.
If I was looking at controlling what the compiler could inline,
then I'd do it the other way around -- small ctor/dtor in
the header, and this larger function out of line in the .c file.

> -    /* Number of usable slots allocated in VECTOR.
> -       VECTOR always has one slot not counted here,
> -       to hold the terminating zero.  */
> -    int allocated;
> -    /* A vector of slots, ALLOCATED + 1 of them.
> -       The first few slots contain strings "VAR=VALUE"
> -       and the next one contains zero.
> -       Then come some unused slots.  */
> -    char **vector;
> -  };
> +    extern char **environ;
> +    gdb_environ e;
> +
> +    if (environ == NULL)
> +      return e;
> +
> +    for (int i = 0; environ[i] != NULL; ++i)
> +      e.m_environ_vector.push_back (xstrdup (environ[i]));
> +
> +    /* The last element of the vector is always going to be NULL.  */
> +    e.m_environ_vector.push_back (NULL);
>  
> -extern struct gdb_environ *make_environ (void);
> +    return e;
> +  }

>    run_target->to_create_inferior (run_target, exec_file,
>  				  std::string (get_inferior_args ()),
> -				  environ_vector (current_inferior ()->environment),
> +				  current_inferior ()
> +				  ->environment.get_char_vector (),
>  				  from_tty);
> @@ -270,7 +270,6 @@ mi_cmd_inferior_tty_show (const char *command, char **argv, int argc)
>  void 
>  _initialize_mi_cmd_env (void)
>  {
> -  struct gdb_environ *environment;
>    const char *env;
>  
>    /* We want original execution path to reset to, if desired later.
> @@ -278,13 +277,10 @@ _initialize_mi_cmd_env (void)
>       current_inferior ()->environment.  Also, there's no obvious
>       place where this code can be moved such that it surely run
>       before any code possibly mangles original PATH.  */
> -  environment = make_environ ();
> -  init_environ (environment);
> -  env = get_in_environ (environment, path_var_name);
> +  env = getenv (path_var_name);
>  
>    /* Can be null if path is not set.  */
>    if (!env)
>      env = "";
>    orig_path = xstrdup (env);
> -  free_environ (environment);
>  }

Please split this change to a separate patch.  Don't we need to
update the comment just above?


> diff --git a/gdb/unittests/environ-selftests.c b/gdb/unittests/environ-selftests.c
> new file mode 100644
> index 0000000..948eacf
> --- /dev/null
> +++ b/gdb/unittests/environ-selftests.c
> @@ -0,0 +1,79 @@
> +/* Self tests for gdb_environ for GDB, the GNU debugger.
> +
> +   Copyright (C) 2017 Free Software Foundation, Inc.
> +
> +   This file is part of GDB.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +#include "defs.h"
> +#include "selftest.h"
> +#include "common/environ.h"
> +
> +namespace selftests {
> +namespace environ {

"environ" as an identifier will be problematic.  Please
pick some other name here.

On some hosts "environ" is a define.  E.g., on my Fedora's
mingw-w64 install I see:

 /usr/x86_64-w64-mingw32/sys-root/mingw/include/stdlib.h:624:#define environ _environ

and gnulib has too:

 $ srcgrep -rn environ gnulib/import/ | grep define
 gnulib/import/extra/snippet/warn-on-use.h:61:   # define environ (*rpl_environ ())
 gnulib/import/unistd.in.h:411:#   define environ (*_NSGetEnviron ())
 gnulib/import/unistd.in.h:432:#  define environ (*rpl_environ ())

I don't think "namespace (*_NSGetEnviron ())" would compile.  :-)


> +
> +static void
> +run_tests ()
> +{
> +  if (setenv ("GDB_SELFTEST_ENVIRON", "1", 1) != 0)
> +    error ("Could not set environment variable for testing.");
> +
> +  gdb_environ env;
> +

Please add a test that that checks that get_char_vector() on an
empty gdb_environ works as expected.  I.e., something like:

   gdb_environ env;
   SELF_CHECK (env.get_char_vector()[0] == NULL);

AFAICS from:

 gdb_environ::gdb_environ ()
 {
 }

 char **
 gdb_environ::get_char_vector () const
 {
   return const_cast<char **> (&m_environ_vector[0]);
 }

we end up with a bogus envp, because it points at
m_environ_vector.end(), which is not a valid pointer
to dereference.  Even ignoring that, it does not point
to NULL.  So if we pass such a pointer to execve or some syscall
that accepts an envp, then it'll try iterating the vector until
it finds a NULL entry, and of course do the wrong thing,
maybe crash if you're lucky.

Note we can get this situation from here too:

  static gdb_environ from_host_environ ()
  {
    extern char **environ;
    gdb_environ e;

    if (environ == NULL)
      return e;


The old code did not suffer from this because it always
allocated gdb_environ::vector:

 struct gdb_environ *
 make_environ (void)
 {
   struct gdb_environ *e;

   e = XNEW (struct gdb_environ);

   e->allocated = 10;
   e->vector = (char **) xmalloc ((e->allocated + 1) * sizeof (char *));
   e->vector[0] = 0;
   return e;
 }

So we either always add a NULL to the vector, or we
change gdb_environ::get_char_vector instead, like:

 char **
 gdb_environ::get_char_vector () const
 {
   if (m_environ_vector.empty ())
     {
       static const char *const empty_envp[1] = { NULL };
       return const_cast<char **> (empty_envp);
     }
   return const_cast<char **> (&m_environ_vector[0]);
 }

This is OK because execve etc. are garanteed to never change
the envp they're passed.

> +  SELF_CHECK (env.get ("PWD") == NULL);
> +
> +  env = gdb_environ::from_host_environ ();
> +
> +  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0);
> +
> +  env.set ("GDB_SELFTEST_ENVIRON", "test");
> +  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "test") == 0);
> +
> +  env.unset ("GDB_SELFTEST_ENVIRON");
> +  SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL);
> +
> +  env.set ("GDB_SELFTEST_ENVIRON", "1");
> +  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0);
> +
> +  env.clear ();
> +  SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL);

Like above, after env.clear() check that this works:

   SELF_CHECK (env.get_char_vector()[0] == NULL);

> +
> +  if (setenv ("GDB_SELFTEST_ENVIRON", "1", 1) != 0)
> +    error ("Could not set environment variable for testing.");

This setenv looks like a stale copy from the one at the top?
I'm not seeing why it's necessary here.

> +
> +  env = gdb_environ::from_host_environ ();
> +  char **penv = env.get_char_vector ();
> +  bool found_var = false, found_twice = false;
> +
> +  for (size_t i = 0; penv[i] != NULL; ++i)
> +    if (strcmp (penv[i], "GDB_SELFTEST_ENVIRON=1") == 0)
> +      {
> +	if (found_var)
> +	  found_twice = true;
> +	found_var = true;
> +      }
> +  SELF_CHECK (found_var == true);
> +  SELF_CHECK (found_twice == false);

Why not simply a count:

  int found_count = 0;
  for (size_t i = 0; penv[i] != NULL; ++i)
    if (strcmp (penv[i], "GDB_SELFTEST_ENVIRON=1") == 0)
      found_count++;
  SELF_CHECK (found_count == 1);


I think that no test actually explicitly sets more than one
var in the vector.  I think you should exercise that.
Also check that removing some other var doesn't remove the
first.  Etc.  E.g., set var 1, set var 2, unset var 1,
check that  var 2 is still there.

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v4] C++ify gdb/common/environ.c
  2017-06-16 15:45   ` Pedro Alves
@ 2017-06-16 18:01     ` Sergio Durigan Junior
  2017-06-16 18:23       ` Pedro Alves
  0 siblings, 1 reply; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-06-16 18:01 UTC (permalink / raw)
  To: Pedro Alves; +Cc: GDB Patches, Simon Marchi

On Friday, June 16 2017, Pedro Alves wrote:

> On 06/14/2017 08:22 PM, Sergio Durigan Junior wrote:
>
>> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
>> index 5e5fcaa..133db1a 100644
>> --- a/gdb/Makefile.in
>> +++ b/gdb/Makefile.in
>> @@ -530,14 +530,16 @@ SUBDIR_UNITTESTS_SRCS = \
>>  	unittests/offset-type-selftests.c \
>>  	unittests/optional-selftests.c \
>>  	unittests/ptid-selftests.c \
>> -	unittests/scoped_restore-selftests.c
>> +	unittests/scoped_restore-selftests.c \
>> +	unittests/environ-selftests.c
>
> Please keep the list sorted.
>
> (I'm guilty of missing that before too.)

Done.

>>  
>>  SUBDIR_UNITTESTS_OBS = \
>>  	function-view-selftests.o \
>>  	offset-type-selftests.o \
>>  	optional-selftests.o \
>>  	ptid-selftests.o \
>> -	scoped_restore-selftests.o
>> +	scoped_restore-selftests.o \
>> +	environ-selftests.o
>
> Ditto.

Done.

>> diff --git a/gdb/common/environ.c b/gdb/common/environ.c
>> index 3145d01..657f2e0 100644
>> --- a/gdb/common/environ.c
>> +++ b/gdb/common/environ.c
>> @@ -18,165 +18,102 @@
>>  #include "common-defs.h"
>>  #include "environ.h"
>>  #include <algorithm>
>> -\f
>> +#include <utility>
>>  
>> -/* Return a new environment object.  */
>> +/* See common/environ.h.  */
>>  
>> -struct gdb_environ *
>> -make_environ (void)
>> +gdb_environ::gdb_environ ()
>>  {
>> -  struct gdb_environ *e;
>> +}
>>  
>> -  e = XNEW (struct gdb_environ);
>> +/* See common/environ.h.  */
>>  
>> -  e->allocated = 10;
>> -  e->vector = (char **) xmalloc ((e->allocated + 1) * sizeof (char *));
>> -  e->vector[0] = 0;
>> -  return e;
>> +gdb_environ::~gdb_environ ()
>> +{
>> +  clear ();
>>  }
>>  
>> -/* Free an environment and all the strings in it.  */
>> +/* See common/environ.h.  */
>>  
>> -void
>> -free_environ (struct gdb_environ *e)
>> +gdb_environ::gdb_environ (gdb_environ &&e)
>> +  : m_environ_vector (std::move (e.m_environ_vector))
>>  {
>> -  char **vector = e->vector;
>> +}
>>  
>> -  while (*vector)
>> -    xfree (*vector++);
>> +/* See common/environ.h.  */
>>  
>> -  xfree (e->vector);
>> -  xfree (e);
>> +gdb_environ &
>> +gdb_environ::operator= (gdb_environ &&e)
>> +{
>> +  clear ();
>> +  m_environ_vector = std::move (e.m_environ_vector);
>> +  return *this;
>>  }
>>  
>> -/* Copy the environment given to this process into E.
>> -   Also copies all the strings in it, so we can be sure
>> -   that all strings in these environments are safe to free.  */
>> +/* See common/environ.h.  */
>>  
>>  void
>> -init_environ (struct gdb_environ *e)
>> +gdb_environ::clear ()
>>  {
>> -  extern char **environ;
>> -  int i;
>> -
>> -  if (environ == NULL)
>> -    return;
>> -
>> -  for (i = 0; environ[i]; i++) /*EMPTY */ ;
>> +  for (char *v : m_environ_vector)
>> +    xfree (v);
>> +  m_environ_vector.clear ();
>> +}
>>  
>> -  if (e->allocated < i)
>> -    {
>> -      e->allocated = std::max (i, e->allocated + 10);
>> -      e->vector = (char **) xrealloc ((char *) e->vector,
>> -				      (e->allocated + 1) * sizeof (char *));
>> -    }
>> +/* See common/environ.h.  */
>>  
>> -  memcpy (e->vector, environ, (i + 1) * sizeof (char *));
>> +const char *
>> +gdb_environ::get (const std::string &var) const
>> +{
>
> Does this need to be a std::string instead of "const char *"?
> Callers always pass a string literal down, so this is going to
> force a deep string dup for no good reason, AFAICS.

It doesn't.  Changed to const char *.

>
>> +  size_t len = var.size ();
>> +  const char *var_str = var.c_str ();
>>  
>> -  while (--i >= 0)
>> -    {
>> -      int len = strlen (e->vector[i]);
>> -      char *newobj = (char *) xmalloc (len + 1);
>> +  for (char *el : m_environ_vector)
>> +    if (el != NULL && strncmp (el, var_str, len) == 0 && el[len] == '=')
>> +      return &el[len + 1];
>>  
>> -      memcpy (newobj, e->vector[i], len + 1);
>> -      e->vector[i] = newobj;
>> -    }
>> +  return NULL;
>>  }
>
>> -char *
>> -get_in_environ (const struct gdb_environ *e, const char *var)
>> +void
>> +gdb_environ::set (const std::string &var, const std::string &value)
>
> Ditto.

Likewise.

>>  {
>> -  int len = strlen (var);
>> -  char **vector = e->vector;
>> -  char *s;
>> -
>> -  for (; (s = *vector) != NULL; vector++)
>> -    if (strncmp (s, var, len) == 0 && s[len] == '=')
>> -      return &s[len + 1];
>> +  /* We have to unset the variable in the vector if it exists.  */
>> +  unset (var);
>>  
>> -  return 0;
>> +  /* Insert the element before the last one, which is always NULL.  */
>> +  m_environ_vector.insert (m_environ_vector.end () - 1,
>> +			   concat (var.c_str (), "=",
>> +				   value.c_str (), NULL));
>>  }
>>  
>> -/* Store the value in E of VAR as VALUE.  */
>> +/* See common/environ.h.  */
>>  
>>  void
>> -set_in_environ (struct gdb_environ *e, const char *var, const char *value)
>> +gdb_environ::unset (const std::string &var)
>
> Ditto.

Likewise.

>
>> -
>> -  return;
>> +  std::string match = var + '=';
>> +  const char *match_str = match.c_str ();
>> +
>> +  /* We iterate until '.cend () - 1' because the last element is
>> +     always NULL.  */
>> +  for (std::vector<char *>::const_iterator el = m_environ_vector.cbegin ();
>> +       el != m_environ_vector.cend () - 1;
>> +       ++el)
>> +    if (startswith (*el, match_str))
>
> In gdb_environ::set you used:

I assume you meant gdb_environ::get, right?

>
>     strncmp (el, var_str, len) == 0 && el[len] == '='
>
> It'd be better if places used the same matching code.  Maybe even put
> this in a separate helper function.  The gdb_environ::set version
> looks better to me for avoiding temporary heap-allocated strings.

Right, done.

>
>> -/* Remove the setting for variable VAR from environment E.  */
>> +/* See common/environ.h.  */
>>  
>> -void
>> -unset_in_environ (struct gdb_environ *e, const char *var)
>> +char **
>> +gdb_environ::get_char_vector () const
>
> So far, getters in gdb's classes don't have a "get_" prefix.
> (except "get()" or course, but that's really a getter in
> the same sense.)  Can we drop it here?  Like:

Yeah, sure.  Simon made this observation in a previous review about the
other methods, but I thought it'd make sense to keep the "get_" prefix
for this specific one.

>
>  char **gdb_environ::char_vector () const
>
> Though I'd rename it like this instead:
>
>  char ** gdb_environ::envp () const
>
> Because that's what the env vector is traditionally called, e.g.,
> from "man 2 execve":
>
>      int execve(const char *filename, char *const argv[],
>                   char *const envp[]);
>
>      int main(int argc, char *argv[], char *envp[])
>
> Likewise I'd use that name for local variables where
> we call gdb_environ::get_char_vector, just to follow
> traditional terminology throughout.

OK, fair enough.  I'll rename it to envp then.  There's just one place
where we assign the return of gdb_environ::get_char_vector to a local
variable (in infcmd.c), so I renamed it accordingly.

>> +/* Class that represents the environment variables as seen by the
>> +   inferior.  */
>> +
>> +class gdb_environ
>> +{
>> +public:
>> +  /* Regular constructor and destructor.  */
>> +  gdb_environ ();
>> +  ~gdb_environ ();
>> +
>> +  /* Move constructor.  */
>> +  gdb_environ (gdb_environ &&e);
>> +
>> +  /* Move assignment.  */
>> +  gdb_environ &operator= (gdb_environ &&e);
>> +
>> +  /* Create a gdb_environ object using the host's environment
>> +     variables.  */
>> +  static gdb_environ from_host_environ ()
>>    {
>
> Nit: I find it a bit odd that the ctors/dtors are short but 
> defined out of line, while this function is defined inline.
> If I was looking at controlling what the compiler could inline,
> then I'd do it the other way around -- small ctor/dtor in
> the header, and this larger function out of line in the .c file.

Question: if I define a method inside the class, does this implicitly
tell the compiler that I want to inline it, as oppose to defining the
method outside?

I'll follow your advice and define the short ctors inside the class, and
move the definition of from_host_environ to the C file.

>> -    /* Number of usable slots allocated in VECTOR.
>> -       VECTOR always has one slot not counted here,
>> -       to hold the terminating zero.  */
>> -    int allocated;
>> -    /* A vector of slots, ALLOCATED + 1 of them.
>> -       The first few slots contain strings "VAR=VALUE"
>> -       and the next one contains zero.
>> -       Then come some unused slots.  */
>> -    char **vector;
>> -  };
>> +    extern char **environ;
>> +    gdb_environ e;
>> +
>> +    if (environ == NULL)
>> +      return e;
>> +
>> +    for (int i = 0; environ[i] != NULL; ++i)
>> +      e.m_environ_vector.push_back (xstrdup (environ[i]));
>> +
>> +    /* The last element of the vector is always going to be NULL.  */
>> +    e.m_environ_vector.push_back (NULL);
>>  
>> -extern struct gdb_environ *make_environ (void);
>> +    return e;
>> +  }
>
>>    run_target->to_create_inferior (run_target, exec_file,
>>  				  std::string (get_inferior_args ()),
>> -				  environ_vector (current_inferior ()->environment),
>> +				  current_inferior ()
>> +				  ->environment.get_char_vector (),
>>  				  from_tty);
>> @@ -270,7 +270,6 @@ mi_cmd_inferior_tty_show (const char *command, char **argv, int argc)
>>  void 
>>  _initialize_mi_cmd_env (void)
>>  {
>> -  struct gdb_environ *environment;
>>    const char *env;
>>  
>>    /* We want original execution path to reset to, if desired later.
>> @@ -278,13 +277,10 @@ _initialize_mi_cmd_env (void)
>>       current_inferior ()->environment.  Also, there's no obvious
>>       place where this code can be moved such that it surely run
>>       before any code possibly mangles original PATH.  */
>> -  environment = make_environ ();
>> -  init_environ (environment);
>> -  env = get_in_environ (environment, path_var_name);
>> +  env = getenv (path_var_name);
>>  
>>    /* Can be null if path is not set.  */
>>    if (!env)
>>      env = "";
>>    orig_path = xstrdup (env);
>> -  free_environ (environment);
>>  }
>
> Please split this change to a separate patch.  Don't we need to
> update the comment just above?

Sure, I'll split and send a separate patch.  And yeah, the comment needs
updating, thanks for noticing that.

>
>> diff --git a/gdb/unittests/environ-selftests.c b/gdb/unittests/environ-selftests.c
>> new file mode 100644
>> index 0000000..948eacf
>> --- /dev/null
>> +++ b/gdb/unittests/environ-selftests.c
>> @@ -0,0 +1,79 @@
>> +/* Self tests for gdb_environ for GDB, the GNU debugger.
>> +
>> +   Copyright (C) 2017 Free Software Foundation, Inc.
>> +
>> +   This file is part of GDB.
>> +
>> +   This program is free software; you can redistribute it and/or modify
>> +   it under the terms of the GNU General Public License as published by
>> +   the Free Software Foundation; either version 3 of the License, or
>> +   (at your option) any later version.
>> +
>> +   This program is distributed in the hope that it will be useful,
>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> +   GNU General Public License for more details.
>> +
>> +   You should have received a copy of the GNU General Public License
>> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
>> +
>> +#include "defs.h"
>> +#include "selftest.h"
>> +#include "common/environ.h"
>> +
>> +namespace selftests {
>> +namespace environ {
>
> "environ" as an identifier will be problematic.  Please
> pick some other name here.
>
> On some hosts "environ" is a define.  E.g., on my Fedora's
> mingw-w64 install I see:
>
>  /usr/x86_64-w64-mingw32/sys-root/mingw/include/stdlib.h:624:#define environ _environ
>
> and gnulib has too:
>
>  $ srcgrep -rn environ gnulib/import/ | grep define
>  gnulib/import/extra/snippet/warn-on-use.h:61:   # define environ (*rpl_environ ())
>  gnulib/import/unistd.in.h:411:#   define environ (*_NSGetEnviron ())
>  gnulib/import/unistd.in.h:432:#  define environ (*rpl_environ ())
>
> I don't think "namespace (*_NSGetEnviron ())" would compile.  :-)

Hah, right :-).  I'll pick "namespace gdb_environ" then.

>
>> +
>> +static void
>> +run_tests ()
>> +{
>> +  if (setenv ("GDB_SELFTEST_ENVIRON", "1", 1) != 0)
>> +    error ("Could not set environment variable for testing.");
>> +
>> +  gdb_environ env;
>> +
>
> Please add a test that that checks that get_char_vector() on an
> empty gdb_environ works as expected.  I.e., something like:
>
>    gdb_environ env;
>    SELF_CHECK (env.get_char_vector()[0] == NULL);

Hm, right, makes sense.  I'll add that.

> AFAICS from:
>
>  gdb_environ::gdb_environ ()
>  {
>  }
>
>  char **
>  gdb_environ::get_char_vector () const
>  {
>    return const_cast<char **> (&m_environ_vector[0]);
>  }
>
> we end up with a bogus envp, because it points at
> m_environ_vector.end(), which is not a valid pointer
> to dereference.  Even ignoring that, it does not point
> to NULL.  So if we pass such a pointer to execve or some syscall
> that accepts an envp, then it'll try iterating the vector until
> it finds a NULL entry, and of course do the wrong thing,
> maybe crash if you're lucky.
>
> Note we can get this situation from here too:
>
>   static gdb_environ from_host_environ ()
>   {
>     extern char **environ;
>     gdb_environ e;
>
>     if (environ == NULL)
>       return e;
>
>
> The old code did not suffer from this because it always
> allocated gdb_environ::vector:
>
>  struct gdb_environ *
>  make_environ (void)
>  {
>    struct gdb_environ *e;
>
>    e = XNEW (struct gdb_environ);
>
>    e->allocated = 10;
>    e->vector = (char **) xmalloc ((e->allocated + 1) * sizeof (char *));
>    e->vector[0] = 0;
>    return e;
>  }
>
> So we either always add a NULL to the vector, or we
> change gdb_environ::get_char_vector instead, like:
>
>  char **
>  gdb_environ::get_char_vector () const
>  {
>    if (m_environ_vector.empty ())
>      {
>        static const char *const empty_envp[1] = { NULL };
>        return const_cast<char **> (empty_envp);
>      }
>    return const_cast<char **> (&m_environ_vector[0]);
>  }
>
> This is OK because execve etc. are garanteed to never change
> the envp they're passed.

Oh, good catch.  I prefer to just initialize the vector with a NULL
value in the ctor; will do that now.

>> +  SELF_CHECK (env.get ("PWD") == NULL);
>> +
>> +  env = gdb_environ::from_host_environ ();
>> +
>> +  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0);
>> +
>> +  env.set ("GDB_SELFTEST_ENVIRON", "test");
>> +  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "test") == 0);
>> +
>> +  env.unset ("GDB_SELFTEST_ENVIRON");
>> +  SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL);
>> +
>> +  env.set ("GDB_SELFTEST_ENVIRON", "1");
>> +  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0);
>> +
>> +  env.clear ();
>> +  SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL);
>
> Like above, after env.clear() check that this works:
>
>    SELF_CHECK (env.get_char_vector()[0] == NULL);

Will do.

>> +
>> +  if (setenv ("GDB_SELFTEST_ENVIRON", "1", 1) != 0)
>> +    error ("Could not set environment variable for testing.");
>
> This setenv looks like a stale copy from the one at the top?
> I'm not seeing why it's necessary here.

It is indeed, thanks.  Removed.

>> +
>> +  env = gdb_environ::from_host_environ ();
>> +  char **penv = env.get_char_vector ();
>> +  bool found_var = false, found_twice = false;
>> +
>> +  for (size_t i = 0; penv[i] != NULL; ++i)
>> +    if (strcmp (penv[i], "GDB_SELFTEST_ENVIRON=1") == 0)
>> +      {
>> +	if (found_var)
>> +	  found_twice = true;
>> +	found_var = true;
>> +      }
>> +  SELF_CHECK (found_var == true);
>> +  SELF_CHECK (found_twice == false);
>
> Why not simply a count:
>
>   int found_count = 0;
>   for (size_t i = 0; penv[i] != NULL; ++i)
>     if (strcmp (penv[i], "GDB_SELFTEST_ENVIRON=1") == 0)
>       found_count++;
>   SELF_CHECK (found_count == 1);

Good idea, thanks.

> I think that no test actually explicitly sets more than one
> var in the vector.  I think you should exercise that.
> Also check that removing some other var doesn't remove the
> first.  Etc.  E.g., set var 1, set var 2, unset var 1,
> check that  var 2 is still there.

Will do.

Thanks for the review.  I'll send v5 soon.

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v4] C++ify gdb/common/environ.c
  2017-06-16 18:01     ` Sergio Durigan Junior
@ 2017-06-16 18:23       ` Pedro Alves
  2017-06-16 21:59         ` Sergio Durigan Junior
  0 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2017-06-16 18:23 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: GDB Patches, Simon Marchi

On 06/16/2017 07:01 PM, Sergio Durigan Junior wrote:
> On Friday, June 16 2017, Pedro Alves wrote:
> 
>> On 06/14/2017 08:22 PM, Sergio Durigan Junior wrote:
>>
>>> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
>>> index 5e5fcaa..133db1a 100644
>>> --- a/gdb/Makefile.in
>>> +++ b/gdb/Makefile.in
>>> @@ -530,14 +530,16 @@ SUBDIR_UNITTESTS_SRCS = \
>>>  	unittests/offset-type-selftests.c \
>>>  	unittests/optional-selftests.c \
>>>  	unittests/ptid-selftests.c \
>>> -	unittests/scoped_restore-selftests.c
>>> +	unittests/scoped_restore-selftests.c \
>>> +	unittests/environ-selftests.c
>>
>> Please keep the list sorted.
>>
>> (I'm guilty of missing that before too.)
> 
> Done.
> 
>>>  
>>>  SUBDIR_UNITTESTS_OBS = \
>>>  	function-view-selftests.o \
>>>  	offset-type-selftests.o \
>>>  	optional-selftests.o \
>>>  	ptid-selftests.o \
>>> -	scoped_restore-selftests.o
>>> +	scoped_restore-selftests.o \
>>> +	environ-selftests.o
>>
>> Ditto.
> 
> Done.
> 
>>> diff --git a/gdb/common/environ.c b/gdb/common/environ.c
>>> index 3145d01..657f2e0 100644
>>> --- a/gdb/common/environ.c
>>> +++ b/gdb/common/environ.c
>>> @@ -18,165 +18,102 @@
>>>  #include "common-defs.h"
>>>  #include "environ.h"
>>>  #include <algorithm>
>>> -\f
>>> +#include <utility>
>>>  
>>> -/* Return a new environment object.  */
>>> +/* See common/environ.h.  */
>>>  
>>> -struct gdb_environ *
>>> -make_environ (void)
>>> +gdb_environ::gdb_environ ()
>>>  {
>>> -  struct gdb_environ *e;
>>> +}
>>>  
>>> -  e = XNEW (struct gdb_environ);
>>> +/* See common/environ.h.  */
>>>  
>>> -  e->allocated = 10;
>>> -  e->vector = (char **) xmalloc ((e->allocated + 1) * sizeof (char *));
>>> -  e->vector[0] = 0;
>>> -  return e;
>>> +gdb_environ::~gdb_environ ()
>>> +{
>>> +  clear ();
>>>  }
>>>  
>>> -/* Free an environment and all the strings in it.  */
>>> +/* See common/environ.h.  */
>>>  
>>> -void
>>> -free_environ (struct gdb_environ *e)
>>> +gdb_environ::gdb_environ (gdb_environ &&e)
>>> +  : m_environ_vector (std::move (e.m_environ_vector))
>>>  {
>>> -  char **vector = e->vector;
>>> +}
>>>  
>>> -  while (*vector)
>>> -    xfree (*vector++);
>>> +/* See common/environ.h.  */
>>>  
>>> -  xfree (e->vector);
>>> -  xfree (e);
>>> +gdb_environ &
>>> +gdb_environ::operator= (gdb_environ &&e)
>>> +{
>>> +  clear ();
>>> +  m_environ_vector = std::move (e.m_environ_vector);
>>> +  return *this;
>>>  }
>>>  
>>> -/* Copy the environment given to this process into E.
>>> -   Also copies all the strings in it, so we can be sure
>>> -   that all strings in these environments are safe to free.  */
>>> +/* See common/environ.h.  */
>>>  
>>>  void
>>> -init_environ (struct gdb_environ *e)
>>> +gdb_environ::clear ()
>>>  {
>>> -  extern char **environ;
>>> -  int i;
>>> -
>>> -  if (environ == NULL)
>>> -    return;
>>> -
>>> -  for (i = 0; environ[i]; i++) /*EMPTY */ ;
>>> +  for (char *v : m_environ_vector)
>>> +    xfree (v);
>>> +  m_environ_vector.clear ();
>>> +}
>>>  
>>> -  if (e->allocated < i)
>>> -    {
>>> -      e->allocated = std::max (i, e->allocated + 10);
>>> -      e->vector = (char **) xrealloc ((char *) e->vector,
>>> -				      (e->allocated + 1) * sizeof (char *));
>>> -    }
>>> +/* See common/environ.h.  */
>>>  
>>> -  memcpy (e->vector, environ, (i + 1) * sizeof (char *));
>>> +const char *
>>> +gdb_environ::get (const std::string &var) const
>>> +{
>>
>> Does this need to be a std::string instead of "const char *"?
>> Callers always pass a string literal down, so this is going to
>> force a deep string dup for no good reason, AFAICS.
> 
> It doesn't.  Changed to const char *.
> 
>>
>>> +  size_t len = var.size ();
>>> +  const char *var_str = var.c_str ();
>>>  
>>> -  while (--i >= 0)
>>> -    {
>>> -      int len = strlen (e->vector[i]);
>>> -      char *newobj = (char *) xmalloc (len + 1);
>>> +  for (char *el : m_environ_vector)
>>> +    if (el != NULL && strncmp (el, var_str, len) == 0 && el[len] == '=')
>>> +      return &el[len + 1];
>>>  
>>> -      memcpy (newobj, e->vector[i], len + 1);
>>> -      e->vector[i] = newobj;
>>> -    }
>>> +  return NULL;
>>>  }
>>
>>> -char *
>>> -get_in_environ (const struct gdb_environ *e, const char *var)
>>> +void
>>> +gdb_environ::set (const std::string &var, const std::string &value)
>>
>> Ditto.
> 
> Likewise.
> 
>>>  {
>>> -  int len = strlen (var);
>>> -  char **vector = e->vector;
>>> -  char *s;
>>> -
>>> -  for (; (s = *vector) != NULL; vector++)
>>> -    if (strncmp (s, var, len) == 0 && s[len] == '=')
>>> -      return &s[len + 1];
>>> +  /* We have to unset the variable in the vector if it exists.  */
>>> +  unset (var);
>>>  
>>> -  return 0;
>>> +  /* Insert the element before the last one, which is always NULL.  */
>>> +  m_environ_vector.insert (m_environ_vector.end () - 1,
>>> +			   concat (var.c_str (), "=",
>>> +				   value.c_str (), NULL));
>>>  }
>>>  
>>> -/* Store the value in E of VAR as VALUE.  */
>>> +/* See common/environ.h.  */
>>>  
>>>  void
>>> -set_in_environ (struct gdb_environ *e, const char *var, const char *value)
>>> +gdb_environ::unset (const std::string &var)
>>
>> Ditto.
> 
> Likewise.
> 
>>
>>> -
>>> -  return;
>>> +  std::string match = var + '=';
>>> +  const char *match_str = match.c_str ();
>>> +
>>> +  /* We iterate until '.cend () - 1' because the last element is
>>> +     always NULL.  */
>>> +  for (std::vector<char *>::const_iterator el = m_environ_vector.cbegin ();
>>> +       el != m_environ_vector.cend () - 1;
>>> +       ++el)
>>> +    if (startswith (*el, match_str))
>>
>> In gdb_environ::set you used:
> 
> I assume you meant gdb_environ::get, right?

Maybe, (I don't have the patch handy anymore.)  Whatever the
other code that looped over all vars.

>> Nit: I find it a bit odd that the ctors/dtors are short but 
>> defined out of line, while this function is defined inline.
>> If I was looking at controlling what the compiler could inline,
>> then I'd do it the other way around -- small ctor/dtor in
>> the header, and this larger function out of line in the .c file.
> 
> Question: if I define a method inside the class, does this implicitly
> tell the compiler that I want to inline it, as oppose to defining the
> method outside?

It's not about inside vs outside.  It's about the compiler seeing the
body when compiling a foo.c file that includes the gdb_environ.h header.
The compiler is invoked on a per-compilation-unit base.  If you put the
method's definition outside the class but still in the gdb_environ.h header,
then the compiler would still be able to inline the method's body in the
foo.c compilation unit if it so chooses.  Of course, then you'd run into
multiple definition problems at link time.  So you can then mark
the definition as inline explicitly.  But that's no different from putting a
free function's definition in a header.

If you put the method instead in gdb_environ.c instead, then when the compiler
is compiling compilation unit foo.c, it has no idea what the body of
the method is, so it can't inline it.  Unless you build with -flto,
of course.

>> So we either always add a NULL to the vector, or we
>> change gdb_environ::get_char_vector instead, like:
>>
>>  char **
>>  gdb_environ::get_char_vector () const
>>  {
>>    if (m_environ_vector.empty ())
>>      {
>>        static const char *const empty_envp[1] = { NULL };
>>        return const_cast<char **> (empty_envp);
>>      }
>>    return const_cast<char **> (&m_environ_vector[0]);
>>  }
>>
>> This is OK because execve etc. are garanteed to never change
>> the envp they're passed.
> 
> Oh, good catch.  I prefer to just initialize the vector with a NULL
> value in the ctor; will do that now.

I'd prefer the other option.  Because then constructing gdb_environ
is dirt cheap and doesn't require heap memory.  We're constructing one
environ per inferior, even if we end up not setting any variable
[now thinking ahead to when we make this work with remote].

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v4] C++ify gdb/common/environ.c
  2017-06-16 18:23       ` Pedro Alves
@ 2017-06-16 21:59         ` Sergio Durigan Junior
  0 siblings, 0 replies; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-06-16 21:59 UTC (permalink / raw)
  To: Pedro Alves; +Cc: GDB Patches, Simon Marchi

On Friday, June 16 2017, Pedro Alves wrote:

>>> Nit: I find it a bit odd that the ctors/dtors are short but 
>>> defined out of line, while this function is defined inline.
>>> If I was looking at controlling what the compiler could inline,
>>> then I'd do it the other way around -- small ctor/dtor in
>>> the header, and this larger function out of line in the .c file.
>> 
>> Question: if I define a method inside the class, does this implicitly
>> tell the compiler that I want to inline it, as oppose to defining the
>> method outside?
>
> It's not about inside vs outside.  It's about the compiler seeing the
> body when compiling a foo.c file that includes the gdb_environ.h header.
> The compiler is invoked on a per-compilation-unit base.  If you put the
> method's definition outside the class but still in the gdb_environ.h header,
> then the compiler would still be able to inline the method's body in the
> foo.c compilation unit if it so chooses.  Of course, then you'd run into
> multiple definition problems at link time.  So you can then mark
> the definition as inline explicitly.  But that's no different from putting a
> free function's definition in a header.
>
> If you put the method instead in gdb_environ.c instead, then when the compiler
> is compiling compilation unit foo.c, it has no idea what the body of
> the method is, so it can't inline it.  Unless you build with -flto,
> of course.

Ah, of course, thanks for the explanation, it makes sense obviously.

>>> So we either always add a NULL to the vector, or we
>>> change gdb_environ::get_char_vector instead, like:
>>>
>>>  char **
>>>  gdb_environ::get_char_vector () const
>>>  {
>>>    if (m_environ_vector.empty ())
>>>      {
>>>        static const char *const empty_envp[1] = { NULL };
>>>        return const_cast<char **> (empty_envp);
>>>      }
>>>    return const_cast<char **> (&m_environ_vector[0]);
>>>  }
>>>
>>> This is OK because execve etc. are garanteed to never change
>>> the envp they're passed.
>> 
>> Oh, good catch.  I prefer to just initialize the vector with a NULL
>> value in the ctor; will do that now.
>
> I'd prefer the other option.  Because then constructing gdb_environ
> is dirt cheap and doesn't require heap memory.  We're constructing one
> environ per inferior, even if we end up not setting any variable
> [now thinking ahead to when we make this work with remote].

I guess I should always implement the option that I *don't* prefer...

Anyway, v5 is ready, should be arriving at your INBOX soon.

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 47+ messages in thread

* [PATCH v5] C++ify gdb/common/environ.c
  2017-04-13  4:05 [PATCH] C++ify gdb/common/environ.c Sergio Durigan Junior
                   ` (2 preceding siblings ...)
  2017-06-14 19:22 ` [PATCH v4] " Sergio Durigan Junior
@ 2017-06-16 22:23 ` Sergio Durigan Junior
  2017-06-17  8:54   ` Simon Marchi
  2017-06-19  4:36 ` [PATCH v6] " Sergio Durigan Junior
                   ` (2 subsequent siblings)
  6 siblings, 1 reply; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-06-16 22:23 UTC (permalink / raw)
  To: GDB Patches; +Cc: Simon Marchi, Pedro Alves, Sergio Durigan Junior

As part of the preparation necessary for my upcoming task, I'd like to
propose that we turn gdb_environ into a class.  The approach taken
here is simple: the class gdb_environ contains everything that is
needed to manipulate the environment variables.  These variables are
stored in an std::vector<char *>, which can be converted to a 'char
**' and passed as argument to functions that need it.

The usage has not changed much.  As per Pedro's suggestion, this class
uses a static factory method initialization.  This means that when an
instance is created, it is initially empty.  When needed, it has to be
initialized using the static method 'from_host_environ'.

As mentioned before, this is a preparation for an upcoming work that I
will be posting in the next few weeks or so.  For that work, I'll
probably create another data structure that will contain all the
environment variables that were set by the user using the 'set
environment' command, because I'll need access to them.  This will be
much easier with the class-ification of gdb_environ.

As noted, this has been regression-tested with the new version of
environ.exp and no regressions were found.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	* Makefile.in (SUBDIR_UNITTESTS_SRCS): Add
	'unittests/environ-selftests.c'.
	(SUBDIR_UNITTESTS_OBS): Add 'environ-selftests.o'.
	* charset.c (find_charset_names): Declare object 'iconv_env'.
	Update code to use 'iconv_env' object.  Remove call to
	'free_environ'.
	* common/environ.c: Include <utility>.
	(make_environ): Delete function.
	(free_environ): Delete function.
	(gdb_environ::clear): New function.
	(gdb_environ::operator=): New function.
	(gdb_environ::get): Likewise.
	(environ_vector): Delete function.
	(set_in_environ): Delete function.
	(gdb_environ::set): New function.
	(unset_in_environ): Delete function.
	(gdb_environ::unset): New function.
	(gdb_environ::envp): Likewise.
	* common/environ.h: Include <vector>.
	(struct gdb_environ): Delete; transform into...
	(class gdb_environ): ... this class.
	(free_environ): Delete prototype.
	(init_environ, get_in_environ, set_in_environ, unset_in_environ,
	environ_vector): Likewise.
	* infcmd.c (run_command_1): Update code to call
	'envp' from 'gdb_environ' class.
	(environment_info): Update code to call methods from 'gdb_environ'
	class.
	(unset_environment_command): Likewise.
	(path_info): Likewise.
	(path_command): Likewise.
	* inferior.c (inferior::~inferior): Delete call to 'free_environ'.
	(inferior::inferior): Initialize 'environment' using the host's
	information.
	* inferior.h: Remove forward declaration of 'struct gdb_environ'.
	Include "environ.h".
	(class inferior) <environment>: Change type from 'struct
	gdb_environ' to 'gdb_environ'.
	* mi/mi-cmd-env.c (mi_cmd_env_path): Update code to call
	methods from 'gdb_environ' class.
	(mi_cmd_inferior_tty_show): Use 'getenv' instead of 'gdb_environ'.
	* solib.c (solib_find_1): Likewise
	* unittests/environ-selftests.c: New file.

gdb/gdbserver/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	* linux-low.c (linux_create_inferior): Adjust code to access the
	environment information via 'gdb_environ' class.
	* lynx-low.c (lynx_create_inferior): Likewise.
	* server.c (our_environ): Make it an instance of 'gdb_environ'.
	(get_environ): Return a pointer to 'our_environ'.
	(captured_main): Initialize 'our_environ'.
	* server.h (get_environ): Adjust prototype.
	* spu-low.c (spu_create_inferior): Adjust code to access the
	environment information via 'gdb_environ' class.
---
 gdb/Makefile.in                   |   2 +
 gdb/charset.c                     |  11 +--
 gdb/common/environ.c              | 201 +++++++++++++++-----------------------
 gdb/common/environ.h              |  59 +++++++----
 gdb/gdbserver/linux-low.c         |   2 +-
 gdb/gdbserver/lynx-low.c          |   2 +-
 gdb/gdbserver/server.c            |   9 +-
 gdb/gdbserver/server.h            |   6 +-
 gdb/gdbserver/spu-low.c           |   2 +-
 gdb/infcmd.c                      |  35 +++----
 gdb/inferior.c                    |   4 +-
 gdb/inferior.h                    |   6 +-
 gdb/mi/mi-cmd-env.c               |   6 +-
 gdb/solib.c                       |   7 +-
 gdb/unittests/environ-selftests.c |  87 +++++++++++++++++
 15 files changed, 247 insertions(+), 192 deletions(-)
 create mode 100644 gdb/unittests/environ-selftests.c

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 5e5fcaa..40b17f9 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -526,6 +526,7 @@ SUBDIR_PYTHON_LDFLAGS =
 SUBDIR_PYTHON_CFLAGS =
 
 SUBDIR_UNITTESTS_SRCS = \
+	unittests/environ-selftests.c \
 	unittests/function-view-selftests.c \
 	unittests/offset-type-selftests.c \
 	unittests/optional-selftests.c \
@@ -533,6 +534,7 @@ SUBDIR_UNITTESTS_SRCS = \
 	unittests/scoped_restore-selftests.c
 
 SUBDIR_UNITTESTS_OBS = \
+	environ-selftests.o \
 	function-view-selftests.o \
 	offset-type-selftests.o \
 	optional-selftests.o \
diff --git a/gdb/charset.c b/gdb/charset.c
index dbe46a4..be95bbe 100644
--- a/gdb/charset.c
+++ b/gdb/charset.c
@@ -794,16 +794,14 @@ find_charset_names (void)
   int err, status;
   int fail = 1;
   int flags;
-  struct gdb_environ *iconv_env;
+  gdb_environ iconv_env = gdb_environ::from_host_environ ();
   char *iconv_program;
 
   /* Older iconvs, e.g. 2.2.2, don't omit the intro text if stdout is
      not a tty.  We need to recognize it and ignore it.  This text is
      subject to translation, so force LANGUAGE=C.  */
-  iconv_env = make_environ ();
-  init_environ (iconv_env);
-  set_in_environ (iconv_env, "LANGUAGE", "C");
-  set_in_environ (iconv_env, "LC_ALL", "C");
+  iconv_env.set ("LANGUAGE", "C");
+  iconv_env.set ("LC_ALL", "C");
 
   child = pex_init (PEX_USE_PIPES, "iconv", NULL);
 
@@ -827,7 +825,7 @@ find_charset_names (void)
   /* Note that we simply ignore errors here.  */
   if (!pex_run_in_environment (child, flags,
 			       args[0], const_cast<char **> (args),
-			       environ_vector (iconv_env),
+			       iconv_env.envp (),
 			       NULL, NULL, &err))
     {
       FILE *in = pex_read_output (child, 0);
@@ -901,7 +899,6 @@ find_charset_names (void)
 
   xfree (iconv_program);
   pex_free (child);
-  free_environ (iconv_env);
 
   if (fail)
     {
diff --git a/gdb/common/environ.c b/gdb/common/environ.c
index 3145d01..c67124b 100644
--- a/gdb/common/environ.c
+++ b/gdb/common/environ.c
@@ -18,165 +18,118 @@
 #include "common-defs.h"
 #include "environ.h"
 #include <algorithm>
-\f
+#include <utility>
 
-/* Return a new environment object.  */
+/* See common/environ.h.  */
 
-struct gdb_environ *
-make_environ (void)
+gdb_environ &
+gdb_environ::operator= (gdb_environ &&e)
 {
-  struct gdb_environ *e;
-
-  e = XNEW (struct gdb_environ);
-
-  e->allocated = 10;
-  e->vector = (char **) xmalloc ((e->allocated + 1) * sizeof (char *));
-  e->vector[0] = 0;
-  return e;
-}
-
-/* Free an environment and all the strings in it.  */
-
-void
-free_environ (struct gdb_environ *e)
-{
-  char **vector = e->vector;
-
-  while (*vector)
-    xfree (*vector++);
-
-  xfree (e->vector);
-  xfree (e);
+  clear ();
+  m_environ_vector = std::move (e.m_environ_vector);
+  return *this;
 }
 
-/* Copy the environment given to this process into E.
-   Also copies all the strings in it, so we can be sure
-   that all strings in these environments are safe to free.  */
-
-void
-init_environ (struct gdb_environ *e)
+/* Create a gdb_environ object using the host's environment
+   variables.  */
+gdb_environ gdb_environ::from_host_environ ()
 {
   extern char **environ;
-  int i;
+  gdb_environ e;
 
   if (environ == NULL)
-    return;
+    return e;
 
-  for (i = 0; environ[i]; i++) /*EMPTY */ ;
+  for (int i = 0; environ[i] != NULL; ++i)
+    e.m_environ_vector.push_back (xstrdup (environ[i]));
 
-  if (e->allocated < i)
-    {
-      e->allocated = std::max (i, e->allocated + 10);
-      e->vector = (char **) xrealloc ((char *) e->vector,
-				      (e->allocated + 1) * sizeof (char *));
-    }
+  /* The last element of the vector is always going to be NULL.  */
+  e.m_environ_vector.push_back (NULL);
 
-  memcpy (e->vector, environ, (i + 1) * sizeof (char *));
+  return e;
+}
 
-  while (--i >= 0)
-    {
-      int len = strlen (e->vector[i]);
-      char *newobj = (char *) xmalloc (len + 1);
+/* See common/environ.h.  */
 
-      memcpy (newobj, e->vector[i], len + 1);
-      e->vector[i] = newobj;
-    }
+void
+gdb_environ::clear ()
+{
+  for (char *v : m_environ_vector)
+    xfree (v);
+  m_environ_vector.clear ();
 }
 
-/* Return the vector of environment E.
-   This is used to get something to pass to execve.  */
+/* Helper function to check if STRING contains an environment variable
+   assignment of VAR, i.e., if STRING starts with 'VAR='.  Return true
+   if it contains, false otherwise.  */
 
-char **
-environ_vector (struct gdb_environ *e)
+static bool
+match_var_in_string (char *string, const char *var, size_t var_len)
 {
-  return e->vector;
+  if (strncmp (string, var, var_len) == 0 && string[var_len] == '=')
+    return true;
+
+  return false;
 }
-\f
-/* Return the value in environment E of variable VAR.  */
 
-char *
-get_in_environ (const struct gdb_environ *e, const char *var)
+/* See common/environ.h.  */
+
+const char *
+gdb_environ::get (const char *var) const
 {
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
+  size_t len = strlen (var);
 
-  for (; (s = *vector) != NULL; vector++)
-    if (strncmp (s, var, len) == 0 && s[len] == '=')
-      return &s[len + 1];
+  for (char *el : m_environ_vector)
+    if (el != NULL && match_var_in_string (el, var, len))
+      return &el[len + 1];
 
-  return 0;
+  return NULL;
 }
 
-/* Store the value in E of VAR as VALUE.  */
+/* See common/environ.h.  */
 
 void
-set_in_environ (struct gdb_environ *e, const char *var, const char *value)
+gdb_environ::set (const char *var, const char *value)
 {
-  int i;
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
-
-  for (i = 0; (s = vector[i]) != NULL; i++)
-    if (strncmp (s, var, len) == 0 && s[len] == '=')
-      break;
+  /* We have to unset the variable in the vector if it exists.  */
+  unset (var);
 
-  if (s == 0)
-    {
-      if (i == e->allocated)
-	{
-	  e->allocated += 10;
-	  vector = (char **) xrealloc ((char *) vector,
-				       (e->allocated + 1) * sizeof (char *));
-	  e->vector = vector;
-	}
-      vector[i + 1] = 0;
-    }
-  else
-    xfree (s);
-
-  s = (char *) xmalloc (len + strlen (value) + 2);
-  strcpy (s, var);
-  strcat (s, "=");
-  strcat (s, value);
-  vector[i] = s;
-
-  /* This used to handle setting the PATH and GNUTARGET variables
-     specially.  The latter has been replaced by "set gnutarget"
-     (which has worked since GDB 4.11).  The former affects searching
-     the PATH to find SHELL, and searching the PATH to find the
-     argument of "symbol-file" or "exec-file".  Maybe we should have
-     some kind of "set exec-path" for that.  But in any event, having
-     "set env" affect anything besides the inferior is a bad idea.
-     What if we want to change the environment we pass to the program
-     without afecting GDB's behavior?  */
-
-  return;
+  /* Insert the element before the last one, which is always NULL.  */
+  m_environ_vector.insert (m_environ_vector.end () - 1,
+			   concat (var, "=", value, NULL));
 }
 
-/* Remove the setting for variable VAR from environment E.  */
+/* See common/environ.h.  */
 
 void
-unset_in_environ (struct gdb_environ *e, const char *var)
+gdb_environ::unset (const char *var)
 {
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
+  size_t len = strlen (var);
+
+  /* We iterate until '.cend () - 1' because the last element is
+     always NULL.  */
+  for (std::vector<char *>::const_iterator el = m_environ_vector.cbegin ();
+       el != m_environ_vector.cend () - 1;
+       ++el)
+    if (match_var_in_string (*el, var, len))
+      {
+	xfree (*el);
+	m_environ_vector.erase (el);
+	break;
+      }
+}
+
+/* See common/environ.h.  */
 
-  for (; (s = *vector) != NULL; vector++)
+char **
+gdb_environ::envp () const
+{
+  if (m_environ_vector.empty ())
     {
-      if (strncmp (s, var, len) == 0 && s[len] == '=')
-	{
-	  xfree (s);
-	  /* Walk through the vector, shuffling args down by one, including
-	     the NULL terminator.  Can't use memcpy() here since the regions
-	     overlap, and memmove() might not be available.  */
-	  while ((vector[0] = vector[1]) != NULL)
-	    {
-	      vector++;
-	    }
-	  break;
-	}
+      static const char *const empty_envp[1] = { NULL };
+
+      return const_cast<char **> (empty_envp);
     }
+
+  return const_cast<char **> (&m_environ_vector[0]);
 }
diff --git a/gdb/common/environ.h b/gdb/common/environ.h
index 3ace69e..7b529b5 100644
--- a/gdb/common/environ.h
+++ b/gdb/common/environ.h
@@ -17,33 +17,54 @@
 #if !defined (ENVIRON_H)
 #define ENVIRON_H 1
 
-/* We manipulate environments represented as these structures.  */
+#include <vector>
 
-struct gdb_environ
+/* Class that represents the environment variables as seen by the
+   inferior.  */
+
+class gdb_environ
+{
+public:
+  /* Regular constructor and destructor.  */
+  gdb_environ () {}
+
+  ~gdb_environ ()
   {
-    /* Number of usable slots allocated in VECTOR.
-       VECTOR always has one slot not counted here,
-       to hold the terminating zero.  */
-    int allocated;
-    /* A vector of slots, ALLOCATED + 1 of them.
-       The first few slots contain strings "VAR=VALUE"
-       and the next one contains zero.
-       Then come some unused slots.  */
-    char **vector;
-  };
+    clear ();
+  }
+
+  /* Move constructor.  */
+  gdb_environ (gdb_environ &&e)
+    : m_environ_vector (std::move (e.m_environ_vector))
+  {}
+
+  /* Move assignment.  */
+  gdb_environ &operator= (gdb_environ &&e);
 
-extern struct gdb_environ *make_environ (void);
+  static gdb_environ from_host_environ ();
 
-extern void free_environ (struct gdb_environ *);
+  /* Clear the environment variables stored in the object.  */
+  void clear ();
 
-extern void init_environ (struct gdb_environ *);
+  /* Return the value in the environment for the variable VAR.  The
+     return pointer is only valid as long as VAR is not
+     removed/replaced from the environment.  */
+  const char *get (const char *var) const;
 
-extern char *get_in_environ (const struct gdb_environ *, const char *);
+  /* Store VAR=VALUE in the environment.  */
+  void set (const char *var, const char *value);
 
-extern void set_in_environ (struct gdb_environ *, const char *, const char *);
+  /* Unset VAR in environment.  */
+  void unset (const char *var);
 
-extern void unset_in_environ (struct gdb_environ *, const char *);
+  /* Return the environment vector represented as a 'char **'.  */
+  char **envp () const;
 
-extern char **environ_vector (struct gdb_environ *);
+private:
+  /* A vector containing the environment variables.  This is useful
+     for when we need to obtain a 'char **' with all the existing
+     variables.  */
+  std::vector<char *> m_environ_vector;
+};
 
 #endif /* defined (ENVIRON_H) */
diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index 7fbf744..5be38f1 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -998,7 +998,7 @@ linux_create_inferior (const char *program,
 
   pid = fork_inferior (program,
 		       str_program_args.c_str (),
-		       environ_vector (get_environ ()), linux_ptrace_fun,
+		       get_environ ()->envp (), linux_ptrace_fun,
 		       NULL, NULL, NULL, NULL);
 
   do_cleanups (restore_personality);
diff --git a/gdb/gdbserver/lynx-low.c b/gdb/gdbserver/lynx-low.c
index 35160d6..77f570e 100644
--- a/gdb/gdbserver/lynx-low.c
+++ b/gdb/gdbserver/lynx-low.c
@@ -259,7 +259,7 @@ lynx_create_inferior (const char *program,
 
   pid = fork_inferior (program,
 		       str_program_args.c_str (),
-		       environ_vector (get_environ ()), lynx_ptrace_fun,
+		       get_environ ()->envp (), lynx_ptrace_fun,
 		       NULL, NULL, NULL, NULL);
 
   post_fork_inferior (pid, program);
diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c
index 1d7a8b0..3838351 100644
--- a/gdb/gdbserver/server.c
+++ b/gdb/gdbserver/server.c
@@ -42,7 +42,7 @@
 
 /* The environment to pass to the inferior when creating it.  */
 
-struct gdb_environ *our_environ = NULL;
+static gdb_environ our_environ;
 
 /* Start the inferior using a shell.  */
 
@@ -257,10 +257,10 @@ get_exec_file (int err)
 
 /* See server.h.  */
 
-struct gdb_environ *
+gdb_environ *
 get_environ ()
 {
-  return our_environ;
+  return &our_environ;
 }
 
 static int
@@ -3698,8 +3698,7 @@ captured_main (int argc, char *argv[])
     }
 
   /* Gather information about the environment.  */
-  our_environ = make_environ ();
-  init_environ (our_environ);
+  our_environ = gdb_environ::from_host_environ ();
 
   initialize_async_io ();
   initialize_low ();
diff --git a/gdb/gdbserver/server.h b/gdb/gdbserver/server.h
index 4de4244..46b614c 100644
--- a/gdb/gdbserver/server.h
+++ b/gdb/gdbserver/server.h
@@ -62,6 +62,7 @@ int vsnprintf(char *str, size_t size, const char *format, va_list ap);
 #include "mem-break.h"
 #include "gdbthread.h"
 #include "inferiors.h"
+#include "environ.h"
 
 /* Target-specific functions */
 
@@ -154,9 +155,8 @@ extern int in_queued_stop_replies (ptid_t ptid);
    inferior and PROGRAM is its name.  */
 extern void post_fork_inferior (int pid, const char *program);
 
-/* Get the 'struct gdb_environ *' being used in the current
-   session.  */
-extern struct gdb_environ *get_environ ();
+/* Get the gdb_environ being used in the current session.  */
+extern gdb_environ *get_environ ();
 
 extern target_waitstatus last_status;
 extern ptid_t last_ptid;
diff --git a/gdb/gdbserver/spu-low.c b/gdb/gdbserver/spu-low.c
index 0f770a0..6362502 100644
--- a/gdb/gdbserver/spu-low.c
+++ b/gdb/gdbserver/spu-low.c
@@ -289,7 +289,7 @@ spu_create_inferior (const char *program,
 
   pid = fork_inferior (program,
 		       str_program_args.c_str (),
-		       environ_vector (get_environ ()), spu_ptrace_fun,
+		       get_environ ()->envp (), spu_ptrace_fun,
 		       NULL, NULL, NULL, NULL);
 
   post_fork_inferior (pid, program);
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index d551639..ee0754d 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -610,7 +610,7 @@ run_command_1 (char *args, int from_tty, int tbreak_at_main)
      the value now.  */
   run_target->to_create_inferior (run_target, exec_file,
 				  std::string (get_inferior_args ()),
-				  environ_vector (current_inferior ()->environment),
+				  current_inferior ()->environment.envp (),
 				  from_tty);
   /* to_create_inferior should push the target, so after this point we
      shouldn't refer to run_target again.  */
@@ -2131,7 +2131,7 @@ environment_info (char *var, int from_tty)
 {
   if (var)
     {
-      char *val = get_in_environ (current_inferior ()->environment, var);
+      const char *val = current_inferior ()->environment.get (var);
 
       if (val)
 	{
@@ -2149,13 +2149,14 @@ environment_info (char *var, int from_tty)
     }
   else
     {
-      char **vector = environ_vector (current_inferior ()->environment);
+      char **envp = current_inferior ()->environment.envp ();
 
-      while (*vector)
-	{
-	  puts_filtered (*vector++);
-	  puts_filtered ("\n");
-	}
+      if (envp != NULL)
+	for (int idx = 0; envp[idx] != NULL; ++idx)
+	  {
+	    puts_filtered (envp[idx]);
+	    puts_filtered ("\n");
+	  }
     }
 }
 
@@ -2215,10 +2216,10 @@ set_environment_command (char *arg, int from_tty)
       printf_filtered (_("Setting environment variable "
 			 "\"%s\" to null value.\n"),
 		       var);
-      set_in_environ (current_inferior ()->environment, var, "");
+      current_inferior ()->environment.set (var, "");
     }
   else
-    set_in_environ (current_inferior ()->environment, var, val);
+    current_inferior ()->environment.set (var, val);
   xfree (var);
 }
 
@@ -2230,13 +2231,10 @@ unset_environment_command (char *var, int from_tty)
       /* If there is no argument, delete all environment variables.
          Ask for confirmation if reading from the terminal.  */
       if (!from_tty || query (_("Delete all environment variables? ")))
-	{
-	  free_environ (current_inferior ()->environment);
-	  current_inferior ()->environment = make_environ ();
-	}
+	current_inferior ()->environment = gdb_environ::from_host_environ ();
     }
   else
-    unset_in_environ (current_inferior ()->environment, var);
+    current_inferior ()->environment.unset (var);
 }
 
 /* Handle the execution path (PATH variable).  */
@@ -2247,8 +2245,7 @@ static void
 path_info (char *args, int from_tty)
 {
   puts_filtered ("Executable and object file path: ");
-  puts_filtered (get_in_environ (current_inferior ()->environment,
-				 path_var_name));
+  puts_filtered (current_inferior ()->environment.get (path_var_name));
   puts_filtered ("\n");
 }
 
@@ -2261,13 +2258,13 @@ path_command (char *dirname, int from_tty)
   const char *env;
 
   dont_repeat ();
-  env = get_in_environ (current_inferior ()->environment, path_var_name);
+  env = current_inferior ()->environment.get (path_var_name);
   /* Can be null if path is not set.  */
   if (!env)
     env = "";
   exec_path = xstrdup (env);
   mod_path (dirname, &exec_path);
-  set_in_environ (current_inferior ()->environment, path_var_name, exec_path);
+  current_inferior ()->environment.set (path_var_name, exec_path);
   xfree (exec_path);
   if (from_tty)
     path_info ((char *) NULL, from_tty);
diff --git a/gdb/inferior.c b/gdb/inferior.c
index 0b655f4..9fa2dad 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -81,7 +81,6 @@ inferior::~inferior ()
   inferior_free_data (inf);
   xfree (inf->args);
   xfree (inf->terminal);
-  free_environ (inf->environment);
   target_desc_info_free (inf->tdesc_info);
   xfree (inf->priv);
 }
@@ -89,10 +88,9 @@ inferior::~inferior ()
 inferior::inferior (int pid_)
   : num (++highest_inferior_num),
     pid (pid_),
-    environment (make_environ ()),
+    environment (gdb_environ::from_host_environ ()),
     registry_data ()
 {
-  init_environ (this->environment);
   inferior_alloc_data (this);
 }
 
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 1c541b7..8ada4f8 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -30,7 +30,6 @@ struct regcache;
 struct ui_out;
 struct terminal_info;
 struct target_desc_info;
-struct gdb_environ;
 struct continuation;
 struct inferior;
 
@@ -43,6 +42,9 @@ struct inferior;
 /* For struct frame_id.  */
 #include "frame.h"
 
+/* For gdb_environ.  */
+#include "environ.h"
+
 #include "progspace.h"
 #include "registry.h"
 
@@ -363,7 +365,7 @@ public:
 
   /* Environment to use for running inferior,
      in format described in environ.h.  */
-  gdb_environ *environment = NULL;
+  gdb_environ environment;
 
   /* True if this child process was attached rather than forked.  */
   bool attach_flag = false;
diff --git a/gdb/mi/mi-cmd-env.c b/gdb/mi/mi-cmd-env.c
index 4093178..a06d08a 100644
--- a/gdb/mi/mi-cmd-env.c
+++ b/gdb/mi/mi-cmd-env.c
@@ -167,7 +167,7 @@ mi_cmd_env_path (const char *command, char **argv, int argc)
   else
     {
       /* Otherwise, get current path to modify.  */
-      env = get_in_environ (current_inferior ()->environment, path_var_name);
+      env = current_inferior ()->environment.get (path_var_name);
 
       /* Can be null if path is not set.  */
       if (!env)
@@ -178,9 +178,9 @@ mi_cmd_env_path (const char *command, char **argv, int argc)
   for (i = argc - 1; i >= 0; --i)
     env_mod_path (argv[i], &exec_path);
 
-  set_in_environ (current_inferior ()->environment, path_var_name, exec_path);
+  current_inferior ()->environment.set (path_var_name, exec_path);
   xfree (exec_path);
-  env = get_in_environ (current_inferior ()->environment, path_var_name);
+  env = current_inferior ()->environment.get (path_var_name);
   uiout->field_string ("path", env);
 }
 
diff --git a/gdb/solib.c b/gdb/solib.c
index 491c18a..788cf15 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -350,16 +350,15 @@ solib_find_1 (const char *in_pathname, int *fd, int is_solib)
 
   /* If not found, next search the inferior's $PATH environment variable.  */
   if (found_file < 0 && sysroot == NULL)
-    found_file = openp (get_in_environ (current_inferior ()->environment,
-					"PATH"),
+    found_file = openp (current_inferior ()->environment.get ("PATH"),
 			OPF_TRY_CWD_FIRST | OPF_RETURN_REALPATH, in_pathname,
 			O_RDONLY | O_BINARY, &temp_pathname);
 
   /* If not found, and we're looking for a solib, next search the
      inferior's $LD_LIBRARY_PATH environment variable.  */
   if (is_solib && found_file < 0 && sysroot == NULL)
-    found_file = openp (get_in_environ (current_inferior ()->environment,
-					"LD_LIBRARY_PATH"),
+    found_file = openp (current_inferior ()->environment.get
+			("LD_LIBRARY_PATH"),
 			OPF_TRY_CWD_FIRST | OPF_RETURN_REALPATH, in_pathname,
 			O_RDONLY | O_BINARY, &temp_pathname);
 
diff --git a/gdb/unittests/environ-selftests.c b/gdb/unittests/environ-selftests.c
new file mode 100644
index 0000000..3757cc5
--- /dev/null
+++ b/gdb/unittests/environ-selftests.c
@@ -0,0 +1,87 @@
+/* Self tests for gdb_environ for GDB, the GNU debugger.
+
+   Copyright (C) 2017 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "selftest.h"
+#include "common/environ.h"
+
+namespace selftests {
+namespace gdb_environ_tests {
+
+static void
+run_tests ()
+{
+  if (setenv ("GDB_SELFTEST_ENVIRON", "1", 1) != 0)
+    error ("Could not set environment variable for testing.");
+
+  gdb_environ env;
+
+  SELF_CHECK (env.envp ()[0] == NULL);
+
+  SELF_CHECK (env.get ("PWD") == NULL);
+
+  env = gdb_environ::from_host_environ ();
+
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0);
+
+  env.set ("GDB_SELFTEST_ENVIRON", "test");
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "test") == 0);
+
+  env.unset ("GDB_SELFTEST_ENVIRON");
+  SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL);
+
+  env.set ("GDB_SELFTEST_ENVIRON", "1");
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0);
+
+  env.clear ();
+
+  SELF_CHECK (env.envp ()[0] == NULL);
+
+  SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL);
+
+  env = gdb_environ::from_host_environ ();
+  char **envp = env.envp ();
+  int num_found = 0;
+
+  for (size_t i = 0; envp[i] != NULL; ++i)
+    if (strcmp (envp[i], "GDB_SELFTEST_ENVIRON=1") == 0)
+      ++num_found;
+
+  SELF_CHECK (num_found == 1);
+
+  unsetenv ("GDB_SELFTEST_ENVIRON");
+
+  env.set ("GDB_SELFTEST_ENVIRON_1", "aaa");
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON_1"), "aaa") == 0);
+
+  env.set ("GDB_SELFTEST_ENVIRON_2", "bbb");
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON_2"), "bbb") == 0);
+
+  env.unset ("GDB_SELFTEST_ENVIRON_1");
+  SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON_1") == NULL);
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON_2"), "bbb") == 0);
+}
+} /* namespace gdb_environ */
+} /* namespace selftests */
+
+void
+_initialize_environ_selftests ()
+{
+  register_self_test (selftests::gdb_environ_tests::run_tests);
+}
-- 
2.9.3

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v5] C++ify gdb/common/environ.c
  2017-06-16 22:23 ` [PATCH v5] " Sergio Durigan Junior
@ 2017-06-17  8:54   ` Simon Marchi
  2017-06-19  4:19     ` Sergio Durigan Junior
  2017-06-19 12:13     ` Pedro Alves
  0 siblings, 2 replies; 47+ messages in thread
From: Simon Marchi @ 2017-06-17  8:54 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: GDB Patches, Pedro Alves

On 2017-06-17 00:23, Sergio Durigan Junior wrote:
>  void
> -set_in_environ (struct gdb_environ *e, const char *var, const char 
> *value)
> +gdb_environ::set (const char *var, const char *value)
>  {
> -  int i;
> -  int len = strlen (var);
> -  char **vector = e->vector;
> -  char *s;
> -
> -  for (i = 0; (s = vector[i]) != NULL; i++)
> -    if (strncmp (s, var, len) == 0 && s[len] == '=')
> -      break;
> +  /* We have to unset the variable in the vector if it exists.  */
> +  unset (var);
> 
> -  if (s == 0)
> -    {
> -      if (i == e->allocated)
> -	{
> -	  e->allocated += 10;
> -	  vector = (char **) xrealloc ((char *) vector,
> -				       (e->allocated + 1) * sizeof (char *));
> -	  e->vector = vector;
> -	}
> -      vector[i + 1] = 0;
> -    }
> -  else
> -    xfree (s);
> -
> -  s = (char *) xmalloc (len + strlen (value) + 2);
> -  strcpy (s, var);
> -  strcat (s, "=");
> -  strcat (s, value);
> -  vector[i] = s;
> -
> -  /* This used to handle setting the PATH and GNUTARGET variables
> -     specially.  The latter has been replaced by "set gnutarget"
> -     (which has worked since GDB 4.11).  The former affects searching
> -     the PATH to find SHELL, and searching the PATH to find the
> -     argument of "symbol-file" or "exec-file".  Maybe we should have
> -     some kind of "set exec-path" for that.  But in any event, having
> -     "set env" affect anything besides the inferior is a bad idea.
> -     What if we want to change the environment we pass to the program
> -     without afecting GDB's behavior?  */
> -
> -  return;
> +  /* Insert the element before the last one, which is always NULL.  */
> +  m_environ_vector.insert (m_environ_vector.end () - 1,
> +			   concat (var, "=", value, NULL));

The breaks if we have just constructed an empty gdb_environ object, as 
the vector is completely empty (no terminating NULL).  So we'd need some 
kind of check before that, if the vector is empty, add a NULL element...

I actually preferred the option of adding the NULL element to the vector 
in the gdb_environ constructor, since it allows always having the vector 
in a consistent state.  I don't think that avoiding that heap allocation 
is worth the complexity it adds to the code (unless we can prove 
otherwise by memory usage profiling).

> +static void
> +run_tests ()
> +{
> +  if (setenv ("GDB_SELFTEST_ENVIRON", "1", 1) != 0)
> +    error ("Could not set environment variable for testing.");
> +
> +  gdb_environ env;
> +
> +  SELF_CHECK (env.envp ()[0] == NULL);
> +
> +  SELF_CHECK (env.get ("PWD") == NULL);

If you add

   env.set ("PWD", "/home");

you should see a crash.

Simon

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v5] C++ify gdb/common/environ.c
  2017-06-17  8:54   ` Simon Marchi
@ 2017-06-19  4:19     ` Sergio Durigan Junior
  2017-06-19 13:40       ` Pedro Alves
  2017-06-19 12:13     ` Pedro Alves
  1 sibling, 1 reply; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-06-19  4:19 UTC (permalink / raw)
  To: Simon Marchi; +Cc: GDB Patches, Pedro Alves

On Saturday, June 17 2017, Simon Marchi wrote:

> On 2017-06-17 00:23, Sergio Durigan Junior wrote:
>>  void
>> -set_in_environ (struct gdb_environ *e, const char *var, const char
>> *value)
>> +gdb_environ::set (const char *var, const char *value)
>>  {
>> -  int i;
>> -  int len = strlen (var);
>> -  char **vector = e->vector;
>> -  char *s;
>> -
>> -  for (i = 0; (s = vector[i]) != NULL; i++)
>> -    if (strncmp (s, var, len) == 0 && s[len] == '=')
>> -      break;
>> +  /* We have to unset the variable in the vector if it exists.  */
>> +  unset (var);
>>
>> -  if (s == 0)
>> -    {
>> -      if (i == e->allocated)
>> -	{
>> -	  e->allocated += 10;
>> -	  vector = (char **) xrealloc ((char *) vector,
>> -				       (e->allocated + 1) * sizeof (char *));
>> -	  e->vector = vector;
>> -	}
>> -      vector[i + 1] = 0;
>> -    }
>> -  else
>> -    xfree (s);
>> -
>> -  s = (char *) xmalloc (len + strlen (value) + 2);
>> -  strcpy (s, var);
>> -  strcat (s, "=");
>> -  strcat (s, value);
>> -  vector[i] = s;
>> -
>> -  /* This used to handle setting the PATH and GNUTARGET variables
>> -     specially.  The latter has been replaced by "set gnutarget"
>> -     (which has worked since GDB 4.11).  The former affects searching
>> -     the PATH to find SHELL, and searching the PATH to find the
>> -     argument of "symbol-file" or "exec-file".  Maybe we should have
>> -     some kind of "set exec-path" for that.  But in any event, having
>> -     "set env" affect anything besides the inferior is a bad idea.
>> -     What if we want to change the environment we pass to the program
>> -     without afecting GDB's behavior?  */
>> -
>> -  return;
>> +  /* Insert the element before the last one, which is always NULL.  */
>> +  m_environ_vector.insert (m_environ_vector.end () - 1,
>> +			   concat (var, "=", value, NULL));
>
> The breaks if we have just constructed an empty gdb_environ object, as
> the vector is completely empty (no terminating NULL).  So we'd need
> some kind of check before that, if the vector is empty, add a NULL
> element...
>
> I actually preferred the option of adding the NULL element to the
> vector in the gdb_environ constructor, since it allows always having
> the vector in a consistent state.  I don't think that avoiding that
> heap allocation is worth the complexity it adds to the code (unless we
> can prove otherwise by memory usage profiling).

I've had some time to think about this, and I agree.  I liked the code
better when it had the ctor doing the initialization of the vector.  I
think it also makes a lot more sense to always initialize the vector
with a NULL element, because this means we're correctly dealing with the
case where there's no environment variable to be passed to the inferior.

I'll update the code and re-submit the patch this way.

>
>> +static void
>> +run_tests ()
>> +{
>> +  if (setenv ("GDB_SELFTEST_ENVIRON", "1", 1) != 0)
>> +    error ("Could not set environment variable for testing.");
>> +
>> +  gdb_environ env;
>> +
>> +  SELF_CHECK (env.envp ()[0] == NULL);
>> +
>> +  SELF_CHECK (env.get ("PWD") == NULL);
>
> If you add
>
>   env.set ("PWD", "/home");
>
> you should see a crash.

Right.  I'll make sure to add this check as well.

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 47+ messages in thread

* [PATCH v6] C++ify gdb/common/environ.c
  2017-04-13  4:05 [PATCH] C++ify gdb/common/environ.c Sergio Durigan Junior
                   ` (3 preceding siblings ...)
  2017-06-16 22:23 ` [PATCH v5] " Sergio Durigan Junior
@ 2017-06-19  4:36 ` Sergio Durigan Junior
  2017-06-19  4:51   ` Sergio Durigan Junior
  2017-06-19 14:26   ` Pedro Alves
  2017-06-19 18:27 ` [PATCH v7] " Sergio Durigan Junior
  2017-06-20  3:27 ` [PATCH v8] " Sergio Durigan Junior
  6 siblings, 2 replies; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-06-19  4:36 UTC (permalink / raw)
  To: GDB Patches; +Cc: Simon Marchi, Pedro Alves, Sergio Durigan Junior

Changes from v5:

- Initializing vector on ctor with NULL element;

- Expanded unittest to make sure we can set/unset an environment
  variable even with an empty gdb_environ.

As part of the preparation necessary for my upcoming task, I'd like to
propose that we turn gdb_environ into a class.  The approach taken
here is simple: the class gdb_environ contains everything that is
needed to manipulate the environment variables.  These variables are
stored in an std::vector<char *>, which can be converted to a 'char
**' and passed as argument to functions that need it.

The usage has not changed much.  As per Pedro's suggestion, this class
uses a static factory method initialization.  This means that when an
instance is created, it is initially empty.  When needed, it has to be
initialized using the static method 'from_host_environ'.

As mentioned before, this is a preparation for an upcoming work that I
will be posting in the next few weeks or so.  For that work, I'll
probably create another data structure that will contain all the
environment variables that were set by the user using the 'set
environment' command, because I'll need access to them.  This will be
much easier with the class-ification of gdb_environ.

As noted, this has been regression-tested with the new version of
environ.exp and no regressions were found.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	* Makefile.in (SUBDIR_UNITTESTS_SRCS): Add
	'unittests/environ-selftests.c'.
	(SUBDIR_UNITTESTS_OBS): Add 'environ-selftests.o'.
	* charset.c (find_charset_names): Declare object 'iconv_env'.
	Update code to use 'iconv_env' object.  Remove call to
	'free_environ'.
	* common/environ.c: Include <utility>.
	(make_environ): Delete function.
	(free_environ): Delete function.
	(gdb_environ::clear): New function.
	(gdb_environ::operator=): New function.
	(gdb_environ::get): Likewise.
	(environ_vector): Delete function.
	(set_in_environ): Delete function.
	(gdb_environ::set): New function.
	(unset_in_environ): Delete function.
	(gdb_environ::unset): New function.
	(gdb_environ::envp): Likewise.
	* common/environ.h: Include <vector>.
	(struct gdb_environ): Delete; transform into...
	(class gdb_environ): ... this class.
	(free_environ): Delete prototype.
	(init_environ, get_in_environ, set_in_environ, unset_in_environ,
	environ_vector): Likewise.
	* infcmd.c (run_command_1): Update code to call
	'envp' from 'gdb_environ' class.
	(environment_info): Update code to call methods from 'gdb_environ'
	class.
	(unset_environment_command): Likewise.
	(path_info): Likewise.
	(path_command): Likewise.
	* inferior.c (inferior::~inferior): Delete call to 'free_environ'.
	(inferior::inferior): Initialize 'environment' using the host's
	information.
	* inferior.h: Remove forward declaration of 'struct gdb_environ'.
	Include "environ.h".
	(class inferior) <environment>: Change type from 'struct
	gdb_environ' to 'gdb_environ'.
	* mi/mi-cmd-env.c (mi_cmd_env_path): Update code to call
	methods from 'gdb_environ' class.
	* solib.c (solib_find_1): Likewise
	* unittests/environ-selftests.c: New file.

gdb/gdbserver/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	* linux-low.c (linux_create_inferior): Adjust code to access the
	environment information via 'gdb_environ' class.
	* lynx-low.c (lynx_create_inferior): Likewise.
	* server.c (our_environ): Make it an instance of 'gdb_environ'.
	(get_environ): Return a pointer to 'our_environ'.
	(captured_main): Initialize 'our_environ'.
	* server.h (get_environ): Adjust prototype.
	* spu-low.c (spu_create_inferior): Adjust code to access the
	environment information via 'gdb_environ' class.
---
 gdb/Makefile.in                   |   2 +
 gdb/charset.c                     |  11 +--
 gdb/common/environ.c              | 199 ++++++++++++++------------------------
 gdb/common/environ.h              |  65 +++++++++----
 gdb/gdbserver/linux-low.c         |   2 +-
 gdb/gdbserver/lynx-low.c          |   2 +-
 gdb/gdbserver/server.c            |   9 +-
 gdb/gdbserver/server.h            |   6 +-
 gdb/gdbserver/spu-low.c           |   2 +-
 gdb/infcmd.c                      |  35 +++----
 gdb/inferior.c                    |   4 +-
 gdb/inferior.h                    |   6 +-
 gdb/mi/mi-cmd-env.c               |   6 +-
 gdb/solib.c                       |   7 +-
 gdb/unittests/environ-selftests.c |  89 +++++++++++++++++
 15 files changed, 252 insertions(+), 193 deletions(-)
 create mode 100644 gdb/unittests/environ-selftests.c

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 153a5dd..b27f698 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -526,6 +526,7 @@ SUBDIR_PYTHON_LDFLAGS =
 SUBDIR_PYTHON_CFLAGS =
 
 SUBDIR_UNITTESTS_SRCS = \
+	unittests/environ-selftests.c \
 	unittests/function-view-selftests.c \
 	unittests/offset-type-selftests.c \
 	unittests/optional-selftests.c \
@@ -533,6 +534,7 @@ SUBDIR_UNITTESTS_SRCS = \
 	unittests/scoped_restore-selftests.c
 
 SUBDIR_UNITTESTS_OBS = \
+	environ-selftests.o \
 	function-view-selftests.o \
 	offset-type-selftests.o \
 	optional-selftests.o \
diff --git a/gdb/charset.c b/gdb/charset.c
index dbe46a4..be95bbe 100644
--- a/gdb/charset.c
+++ b/gdb/charset.c
@@ -794,16 +794,14 @@ find_charset_names (void)
   int err, status;
   int fail = 1;
   int flags;
-  struct gdb_environ *iconv_env;
+  gdb_environ iconv_env = gdb_environ::from_host_environ ();
   char *iconv_program;
 
   /* Older iconvs, e.g. 2.2.2, don't omit the intro text if stdout is
      not a tty.  We need to recognize it and ignore it.  This text is
      subject to translation, so force LANGUAGE=C.  */
-  iconv_env = make_environ ();
-  init_environ (iconv_env);
-  set_in_environ (iconv_env, "LANGUAGE", "C");
-  set_in_environ (iconv_env, "LC_ALL", "C");
+  iconv_env.set ("LANGUAGE", "C");
+  iconv_env.set ("LC_ALL", "C");
 
   child = pex_init (PEX_USE_PIPES, "iconv", NULL);
 
@@ -827,7 +825,7 @@ find_charset_names (void)
   /* Note that we simply ignore errors here.  */
   if (!pex_run_in_environment (child, flags,
 			       args[0], const_cast<char **> (args),
-			       environ_vector (iconv_env),
+			       iconv_env.envp (),
 			       NULL, NULL, &err))
     {
       FILE *in = pex_read_output (child, 0);
@@ -901,7 +899,6 @@ find_charset_names (void)
 
   xfree (iconv_program);
   pex_free (child);
-  free_environ (iconv_env);
 
   if (fail)
     {
diff --git a/gdb/common/environ.c b/gdb/common/environ.c
index 3145d01..09860c9 100644
--- a/gdb/common/environ.c
+++ b/gdb/common/environ.c
@@ -18,165 +18,114 @@
 #include "common-defs.h"
 #include "environ.h"
 #include <algorithm>
-\f
+#include <utility>
 
-/* Return a new environment object.  */
+/* See common/environ.h.  */
 
-struct gdb_environ *
-make_environ (void)
+gdb_environ &
+gdb_environ::operator= (gdb_environ &&e)
 {
-  struct gdb_environ *e;
-
-  e = XNEW (struct gdb_environ);
-
-  e->allocated = 10;
-  e->vector = (char **) xmalloc ((e->allocated + 1) * sizeof (char *));
-  e->vector[0] = 0;
-  return e;
-}
-
-/* Free an environment and all the strings in it.  */
-
-void
-free_environ (struct gdb_environ *e)
-{
-  char **vector = e->vector;
-
-  while (*vector)
-    xfree (*vector++);
-
-  xfree (e->vector);
-  xfree (e);
+  clear ();
+  m_environ_vector = std::move (e.m_environ_vector);
+  return *this;
 }
 
-/* Copy the environment given to this process into E.
-   Also copies all the strings in it, so we can be sure
-   that all strings in these environments are safe to free.  */
-
-void
-init_environ (struct gdb_environ *e)
+/* Create a gdb_environ object using the host's environment
+   variables.  */
+gdb_environ gdb_environ::from_host_environ ()
 {
   extern char **environ;
-  int i;
+  gdb_environ e;
 
   if (environ == NULL)
-    return;
+    return e;
 
-  for (i = 0; environ[i]; i++) /*EMPTY */ ;
-
-  if (e->allocated < i)
+  for (int i = 0; environ[i] != NULL; ++i)
     {
-      e->allocated = std::max (i, e->allocated + 10);
-      e->vector = (char **) xrealloc ((char *) e->vector,
-				      (e->allocated + 1) * sizeof (char *));
+      /* Make sure we add the element before the last (NULL).  */
+      e.m_environ_vector.insert (e.m_environ_vector.end () - 1,
+				 xstrdup (environ[i]));
     }
 
-  memcpy (e->vector, environ, (i + 1) * sizeof (char *));
+  return e;
+}
 
-  while (--i >= 0)
-    {
-      int len = strlen (e->vector[i]);
-      char *newobj = (char *) xmalloc (len + 1);
+/* See common/environ.h.  */
 
-      memcpy (newobj, e->vector[i], len + 1);
-      e->vector[i] = newobj;
-    }
+void
+gdb_environ::clear ()
+{
+  for (char *v : m_environ_vector)
+    xfree (v);
+  m_environ_vector.clear ();
+  /* Always add the NULL element.  */
+  m_environ_vector.push_back (NULL);
 }
 
-/* Return the vector of environment E.
-   This is used to get something to pass to execve.  */
+/* Helper function to check if STRING contains an environment variable
+   assignment of VAR, i.e., if STRING starts with 'VAR='.  Return true
+   if it contains, false otherwise.  */
 
-char **
-environ_vector (struct gdb_environ *e)
+static bool
+match_var_in_string (char *string, const char *var, size_t var_len)
 {
-  return e->vector;
+  if (strncmp (string, var, var_len) == 0 && string[var_len] == '=')
+    return true;
+
+  return false;
 }
-\f
-/* Return the value in environment E of variable VAR.  */
 
-char *
-get_in_environ (const struct gdb_environ *e, const char *var)
+/* See common/environ.h.  */
+
+const char *
+gdb_environ::get (const char *var) const
 {
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
+  size_t len = strlen (var);
 
-  for (; (s = *vector) != NULL; vector++)
-    if (strncmp (s, var, len) == 0 && s[len] == '=')
-      return &s[len + 1];
+  for (char *el : m_environ_vector)
+    if (el != NULL && match_var_in_string (el, var, len))
+      return &el[len + 1];
 
-  return 0;
+  return NULL;
 }
 
-/* Store the value in E of VAR as VALUE.  */
+/* See common/environ.h.  */
 
 void
-set_in_environ (struct gdb_environ *e, const char *var, const char *value)
+gdb_environ::set (const char *var, const char *value)
 {
-  int i;
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
+  /* We have to unset the variable in the vector if it exists.  */
+  unset (var);
 
-  for (i = 0; (s = vector[i]) != NULL; i++)
-    if (strncmp (s, var, len) == 0 && s[len] == '=')
-      break;
-
-  if (s == 0)
-    {
-      if (i == e->allocated)
-	{
-	  e->allocated += 10;
-	  vector = (char **) xrealloc ((char *) vector,
-				       (e->allocated + 1) * sizeof (char *));
-	  e->vector = vector;
-	}
-      vector[i + 1] = 0;
-    }
-  else
-    xfree (s);
-
-  s = (char *) xmalloc (len + strlen (value) + 2);
-  strcpy (s, var);
-  strcat (s, "=");
-  strcat (s, value);
-  vector[i] = s;
-
-  /* This used to handle setting the PATH and GNUTARGET variables
-     specially.  The latter has been replaced by "set gnutarget"
-     (which has worked since GDB 4.11).  The former affects searching
-     the PATH to find SHELL, and searching the PATH to find the
-     argument of "symbol-file" or "exec-file".  Maybe we should have
-     some kind of "set exec-path" for that.  But in any event, having
-     "set env" affect anything besides the inferior is a bad idea.
-     What if we want to change the environment we pass to the program
-     without afecting GDB's behavior?  */
-
-  return;
+  /* Insert the element before the last one, which is always NULL.  */
+  m_environ_vector.insert (m_environ_vector.end () - 1,
+			   concat (var, "=", value, NULL));
 }
 
-/* Remove the setting for variable VAR from environment E.  */
+/* See common/environ.h.  */
 
 void
-unset_in_environ (struct gdb_environ *e, const char *var)
+gdb_environ::unset (const char *var)
 {
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
+  size_t len = strlen (var);
+
+  /* We iterate until '.cend () - 1' because the last element is
+     always NULL.  */
+  for (std::vector<char *>::const_iterator el = m_environ_vector.cbegin ();
+       el != m_environ_vector.cend () - 1;
+       ++el)
+    if (match_var_in_string (*el, var, len))
+      {
+	xfree (*el);
+	m_environ_vector.erase (el);
+	break;
+      }
+}
 
-  for (; (s = *vector) != NULL; vector++)
-    {
-      if (strncmp (s, var, len) == 0 && s[len] == '=')
-	{
-	  xfree (s);
-	  /* Walk through the vector, shuffling args down by one, including
-	     the NULL terminator.  Can't use memcpy() here since the regions
-	     overlap, and memmove() might not be available.  */
-	  while ((vector[0] = vector[1]) != NULL)
-	    {
-	      vector++;
-	    }
-	  break;
-	}
-    }
+/* See common/environ.h.  */
+
+char **
+gdb_environ::envp () const
+{
+  return const_cast<char **> (&m_environ_vector[0]);
 }
diff --git a/gdb/common/environ.h b/gdb/common/environ.h
index 3ace69e..b882bc7 100644
--- a/gdb/common/environ.h
+++ b/gdb/common/environ.h
@@ -17,33 +17,60 @@
 #if !defined (ENVIRON_H)
 #define ENVIRON_H 1
 
-/* We manipulate environments represented as these structures.  */
+#include <vector>
 
-struct gdb_environ
+/* Class that represents the environment variables as seen by the
+   inferior.  */
+
+class gdb_environ
+{
+public:
+  /* Regular constructor and destructor.  */
+  gdb_environ ()
+  {
+    /* Make sure that the vector contains at least a NULL element.
+       If/when we add more variables to it, NULL will always be the
+       last element.  */
+    m_environ_vector.push_back (NULL);
+  }
+
+  ~gdb_environ ()
   {
-    /* Number of usable slots allocated in VECTOR.
-       VECTOR always has one slot not counted here,
-       to hold the terminating zero.  */
-    int allocated;
-    /* A vector of slots, ALLOCATED + 1 of them.
-       The first few slots contain strings "VAR=VALUE"
-       and the next one contains zero.
-       Then come some unused slots.  */
-    char **vector;
-  };
+    clear ();
+  }
+
+  /* Move constructor.  */
+  gdb_environ (gdb_environ &&e)
+    : m_environ_vector (std::move (e.m_environ_vector))
+  {}
+
+  /* Move assignment.  */
+  gdb_environ &operator= (gdb_environ &&e);
 
-extern struct gdb_environ *make_environ (void);
+  static gdb_environ from_host_environ ();
 
-extern void free_environ (struct gdb_environ *);
+  /* Clear the environment variables stored in the object.  */
+  void clear ();
 
-extern void init_environ (struct gdb_environ *);
+  /* Return the value in the environment for the variable VAR.  The
+     return pointer is only valid as long as VAR is not
+     removed/replaced from the environment.  */
+  const char *get (const char *var) const;
 
-extern char *get_in_environ (const struct gdb_environ *, const char *);
+  /* Store VAR=VALUE in the environment.  */
+  void set (const char *var, const char *value);
 
-extern void set_in_environ (struct gdb_environ *, const char *, const char *);
+  /* Unset VAR in environment.  */
+  void unset (const char *var);
 
-extern void unset_in_environ (struct gdb_environ *, const char *);
+  /* Return the environment vector represented as a 'char **'.  */
+  char **envp () const;
 
-extern char **environ_vector (struct gdb_environ *);
+private:
+  /* A vector containing the environment variables.  This is useful
+     for when we need to obtain a 'char **' with all the existing
+     variables.  */
+  std::vector<char *> m_environ_vector;
+};
 
 #endif /* defined (ENVIRON_H) */
diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index c8e8d08..3d7cfe3 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -998,7 +998,7 @@ linux_create_inferior (const char *program,
 
   pid = fork_inferior (program,
 		       str_program_args.c_str (),
-		       environ_vector (get_environ ()), linux_ptrace_fun,
+		       get_environ ()->envp (), linux_ptrace_fun,
 		       NULL, NULL, NULL, NULL);
 
   do_cleanups (restore_personality);
diff --git a/gdb/gdbserver/lynx-low.c b/gdb/gdbserver/lynx-low.c
index 35160d6..77f570e 100644
--- a/gdb/gdbserver/lynx-low.c
+++ b/gdb/gdbserver/lynx-low.c
@@ -259,7 +259,7 @@ lynx_create_inferior (const char *program,
 
   pid = fork_inferior (program,
 		       str_program_args.c_str (),
-		       environ_vector (get_environ ()), lynx_ptrace_fun,
+		       get_environ ()->envp (), lynx_ptrace_fun,
 		       NULL, NULL, NULL, NULL);
 
   post_fork_inferior (pid, program);
diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c
index 1d7a8b0..3838351 100644
--- a/gdb/gdbserver/server.c
+++ b/gdb/gdbserver/server.c
@@ -42,7 +42,7 @@
 
 /* The environment to pass to the inferior when creating it.  */
 
-struct gdb_environ *our_environ = NULL;
+static gdb_environ our_environ;
 
 /* Start the inferior using a shell.  */
 
@@ -257,10 +257,10 @@ get_exec_file (int err)
 
 /* See server.h.  */
 
-struct gdb_environ *
+gdb_environ *
 get_environ ()
 {
-  return our_environ;
+  return &our_environ;
 }
 
 static int
@@ -3698,8 +3698,7 @@ captured_main (int argc, char *argv[])
     }
 
   /* Gather information about the environment.  */
-  our_environ = make_environ ();
-  init_environ (our_environ);
+  our_environ = gdb_environ::from_host_environ ();
 
   initialize_async_io ();
   initialize_low ();
diff --git a/gdb/gdbserver/server.h b/gdb/gdbserver/server.h
index 4de4244..46b614c 100644
--- a/gdb/gdbserver/server.h
+++ b/gdb/gdbserver/server.h
@@ -62,6 +62,7 @@ int vsnprintf(char *str, size_t size, const char *format, va_list ap);
 #include "mem-break.h"
 #include "gdbthread.h"
 #include "inferiors.h"
+#include "environ.h"
 
 /* Target-specific functions */
 
@@ -154,9 +155,8 @@ extern int in_queued_stop_replies (ptid_t ptid);
    inferior and PROGRAM is its name.  */
 extern void post_fork_inferior (int pid, const char *program);
 
-/* Get the 'struct gdb_environ *' being used in the current
-   session.  */
-extern struct gdb_environ *get_environ ();
+/* Get the gdb_environ being used in the current session.  */
+extern gdb_environ *get_environ ();
 
 extern target_waitstatus last_status;
 extern ptid_t last_ptid;
diff --git a/gdb/gdbserver/spu-low.c b/gdb/gdbserver/spu-low.c
index 0f770a0..6362502 100644
--- a/gdb/gdbserver/spu-low.c
+++ b/gdb/gdbserver/spu-low.c
@@ -289,7 +289,7 @@ spu_create_inferior (const char *program,
 
   pid = fork_inferior (program,
 		       str_program_args.c_str (),
-		       environ_vector (get_environ ()), spu_ptrace_fun,
+		       get_environ ()->envp (), spu_ptrace_fun,
 		       NULL, NULL, NULL, NULL);
 
   post_fork_inferior (pid, program);
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index d551639..ee0754d 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -610,7 +610,7 @@ run_command_1 (char *args, int from_tty, int tbreak_at_main)
      the value now.  */
   run_target->to_create_inferior (run_target, exec_file,
 				  std::string (get_inferior_args ()),
-				  environ_vector (current_inferior ()->environment),
+				  current_inferior ()->environment.envp (),
 				  from_tty);
   /* to_create_inferior should push the target, so after this point we
      shouldn't refer to run_target again.  */
@@ -2131,7 +2131,7 @@ environment_info (char *var, int from_tty)
 {
   if (var)
     {
-      char *val = get_in_environ (current_inferior ()->environment, var);
+      const char *val = current_inferior ()->environment.get (var);
 
       if (val)
 	{
@@ -2149,13 +2149,14 @@ environment_info (char *var, int from_tty)
     }
   else
     {
-      char **vector = environ_vector (current_inferior ()->environment);
+      char **envp = current_inferior ()->environment.envp ();
 
-      while (*vector)
-	{
-	  puts_filtered (*vector++);
-	  puts_filtered ("\n");
-	}
+      if (envp != NULL)
+	for (int idx = 0; envp[idx] != NULL; ++idx)
+	  {
+	    puts_filtered (envp[idx]);
+	    puts_filtered ("\n");
+	  }
     }
 }
 
@@ -2215,10 +2216,10 @@ set_environment_command (char *arg, int from_tty)
       printf_filtered (_("Setting environment variable "
 			 "\"%s\" to null value.\n"),
 		       var);
-      set_in_environ (current_inferior ()->environment, var, "");
+      current_inferior ()->environment.set (var, "");
     }
   else
-    set_in_environ (current_inferior ()->environment, var, val);
+    current_inferior ()->environment.set (var, val);
   xfree (var);
 }
 
@@ -2230,13 +2231,10 @@ unset_environment_command (char *var, int from_tty)
       /* If there is no argument, delete all environment variables.
          Ask for confirmation if reading from the terminal.  */
       if (!from_tty || query (_("Delete all environment variables? ")))
-	{
-	  free_environ (current_inferior ()->environment);
-	  current_inferior ()->environment = make_environ ();
-	}
+	current_inferior ()->environment = gdb_environ::from_host_environ ();
     }
   else
-    unset_in_environ (current_inferior ()->environment, var);
+    current_inferior ()->environment.unset (var);
 }
 
 /* Handle the execution path (PATH variable).  */
@@ -2247,8 +2245,7 @@ static void
 path_info (char *args, int from_tty)
 {
   puts_filtered ("Executable and object file path: ");
-  puts_filtered (get_in_environ (current_inferior ()->environment,
-				 path_var_name));
+  puts_filtered (current_inferior ()->environment.get (path_var_name));
   puts_filtered ("\n");
 }
 
@@ -2261,13 +2258,13 @@ path_command (char *dirname, int from_tty)
   const char *env;
 
   dont_repeat ();
-  env = get_in_environ (current_inferior ()->environment, path_var_name);
+  env = current_inferior ()->environment.get (path_var_name);
   /* Can be null if path is not set.  */
   if (!env)
     env = "";
   exec_path = xstrdup (env);
   mod_path (dirname, &exec_path);
-  set_in_environ (current_inferior ()->environment, path_var_name, exec_path);
+  current_inferior ()->environment.set (path_var_name, exec_path);
   xfree (exec_path);
   if (from_tty)
     path_info ((char *) NULL, from_tty);
diff --git a/gdb/inferior.c b/gdb/inferior.c
index 0b655f4..9fa2dad 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -81,7 +81,6 @@ inferior::~inferior ()
   inferior_free_data (inf);
   xfree (inf->args);
   xfree (inf->terminal);
-  free_environ (inf->environment);
   target_desc_info_free (inf->tdesc_info);
   xfree (inf->priv);
 }
@@ -89,10 +88,9 @@ inferior::~inferior ()
 inferior::inferior (int pid_)
   : num (++highest_inferior_num),
     pid (pid_),
-    environment (make_environ ()),
+    environment (gdb_environ::from_host_environ ()),
     registry_data ()
 {
-  init_environ (this->environment);
   inferior_alloc_data (this);
 }
 
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 1c541b7..8ada4f8 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -30,7 +30,6 @@ struct regcache;
 struct ui_out;
 struct terminal_info;
 struct target_desc_info;
-struct gdb_environ;
 struct continuation;
 struct inferior;
 
@@ -43,6 +42,9 @@ struct inferior;
 /* For struct frame_id.  */
 #include "frame.h"
 
+/* For gdb_environ.  */
+#include "environ.h"
+
 #include "progspace.h"
 #include "registry.h"
 
@@ -363,7 +365,7 @@ public:
 
   /* Environment to use for running inferior,
      in format described in environ.h.  */
-  gdb_environ *environment = NULL;
+  gdb_environ environment;
 
   /* True if this child process was attached rather than forked.  */
   bool attach_flag = false;
diff --git a/gdb/mi/mi-cmd-env.c b/gdb/mi/mi-cmd-env.c
index 97be139..bf4578c 100644
--- a/gdb/mi/mi-cmd-env.c
+++ b/gdb/mi/mi-cmd-env.c
@@ -167,7 +167,7 @@ mi_cmd_env_path (const char *command, char **argv, int argc)
   else
     {
       /* Otherwise, get current path to modify.  */
-      env = get_in_environ (current_inferior ()->environment, path_var_name);
+      env = current_inferior ()->environment.get (path_var_name);
 
       /* Can be null if path is not set.  */
       if (!env)
@@ -178,9 +178,9 @@ mi_cmd_env_path (const char *command, char **argv, int argc)
   for (i = argc - 1; i >= 0; --i)
     env_mod_path (argv[i], &exec_path);
 
-  set_in_environ (current_inferior ()->environment, path_var_name, exec_path);
+  current_inferior ()->environment.set (path_var_name, exec_path);
   xfree (exec_path);
-  env = get_in_environ (current_inferior ()->environment, path_var_name);
+  env = current_inferior ()->environment.get (path_var_name);
   uiout->field_string ("path", env);
 }
 
diff --git a/gdb/solib.c b/gdb/solib.c
index 491c18a..788cf15 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -350,16 +350,15 @@ solib_find_1 (const char *in_pathname, int *fd, int is_solib)
 
   /* If not found, next search the inferior's $PATH environment variable.  */
   if (found_file < 0 && sysroot == NULL)
-    found_file = openp (get_in_environ (current_inferior ()->environment,
-					"PATH"),
+    found_file = openp (current_inferior ()->environment.get ("PATH"),
 			OPF_TRY_CWD_FIRST | OPF_RETURN_REALPATH, in_pathname,
 			O_RDONLY | O_BINARY, &temp_pathname);
 
   /* If not found, and we're looking for a solib, next search the
      inferior's $LD_LIBRARY_PATH environment variable.  */
   if (is_solib && found_file < 0 && sysroot == NULL)
-    found_file = openp (get_in_environ (current_inferior ()->environment,
-					"LD_LIBRARY_PATH"),
+    found_file = openp (current_inferior ()->environment.get
+			("LD_LIBRARY_PATH"),
 			OPF_TRY_CWD_FIRST | OPF_RETURN_REALPATH, in_pathname,
 			O_RDONLY | O_BINARY, &temp_pathname);
 
diff --git a/gdb/unittests/environ-selftests.c b/gdb/unittests/environ-selftests.c
new file mode 100644
index 0000000..0bcc6cc
--- /dev/null
+++ b/gdb/unittests/environ-selftests.c
@@ -0,0 +1,89 @@
+/* Self tests for gdb_environ for GDB, the GNU debugger.
+
+   Copyright (C) 2017 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "selftest.h"
+#include "common/environ.h"
+
+namespace selftests {
+namespace gdb_environ_tests {
+
+static void
+run_tests ()
+{
+  if (setenv ("GDB_SELFTEST_ENVIRON", "1", 1) != 0)
+    error ("Could not set environment variable for testing.");
+
+  gdb_environ env;
+
+  SELF_CHECK (env.envp ()[0] == NULL);
+
+  SELF_CHECK (env.get ("PWD") == NULL);
+  env.set ("PWD", "test");
+  env.unset ("PWD");
+
+  env = gdb_environ::from_host_environ ();
+
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0);
+
+  env.set ("GDB_SELFTEST_ENVIRON", "test");
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "test") == 0);
+
+  env.unset ("GDB_SELFTEST_ENVIRON");
+  SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL);
+
+  env.set ("GDB_SELFTEST_ENVIRON", "1");
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0);
+
+  env.clear ();
+
+  SELF_CHECK (env.envp ()[0] == NULL);
+
+  SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL);
+
+  env = gdb_environ::from_host_environ ();
+  char **envp = env.envp ();
+  int num_found = 0;
+
+  for (size_t i = 0; envp[i] != NULL; ++i)
+    if (strcmp (envp[i], "GDB_SELFTEST_ENVIRON=1") == 0)
+      ++num_found;
+
+  SELF_CHECK (num_found == 1);
+
+  unsetenv ("GDB_SELFTEST_ENVIRON");
+
+  env.set ("GDB_SELFTEST_ENVIRON_1", "aaa");
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON_1"), "aaa") == 0);
+
+  env.set ("GDB_SELFTEST_ENVIRON_2", "bbb");
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON_2"), "bbb") == 0);
+
+  env.unset ("GDB_SELFTEST_ENVIRON_1");
+  SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON_1") == NULL);
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON_2"), "bbb") == 0);
+}
+} /* namespace gdb_environ */
+} /* namespace selftests */
+
+void
+_initialize_environ_selftests ()
+{
+  register_self_test (selftests::gdb_environ_tests::run_tests);
+}
-- 
2.9.3

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v6] C++ify gdb/common/environ.c
  2017-06-19  4:36 ` [PATCH v6] " Sergio Durigan Junior
@ 2017-06-19  4:51   ` Sergio Durigan Junior
  2017-06-19  7:18     ` Simon Marchi
  2017-06-19 14:26   ` Pedro Alves
  1 sibling, 1 reply; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-06-19  4:51 UTC (permalink / raw)
  To: GDB Patches; +Cc: Simon Marchi, Pedro Alves

On Monday, June 19 2017, I wrote:

> [...]
> -struct gdb_environ *
> -make_environ (void)
> +gdb_environ &
> +gdb_environ::operator= (gdb_environ &&e)
>  {
> -  struct gdb_environ *e;
> -
> -  e = XNEW (struct gdb_environ);
> -
> -  e->allocated = 10;
> -  e->vector = (char **) xmalloc ((e->allocated + 1) * sizeof (char *));
> -  e->vector[0] = 0;
> -  return e;
> -}
> -
> -/* Free an environment and all the strings in it.  */
> -
> -void
> -free_environ (struct gdb_environ *e)
> -{
> -  char **vector = e->vector;
> -
> -  while (*vector)
> -    xfree (*vector++);
> -
> -  xfree (e->vector);
> -  xfree (e);
> +  clear ();
> +  m_environ_vector = std::move (e.m_environ_vector);
> +  return *this;
>  }

I should probably do an m_environ_vector.clear () before doing the
std::move, because the vector will contain the NULL element.  I'll make
sure to do this before I push the patch.

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v6] C++ify gdb/common/environ.c
  2017-06-19  4:51   ` Sergio Durigan Junior
@ 2017-06-19  7:18     ` Simon Marchi
  2017-06-19 14:26       ` Pedro Alves
  0 siblings, 1 reply; 47+ messages in thread
From: Simon Marchi @ 2017-06-19  7:18 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: GDB Patches, Pedro Alves

On 2017-06-19 06:51, Sergio Durigan Junior wrote:
> On Monday, June 19 2017, I wrote:
> 
>> [...]
>> -struct gdb_environ *
>> -make_environ (void)
>> +gdb_environ &
>> +gdb_environ::operator= (gdb_environ &&e)
>>  {
>> -  struct gdb_environ *e;
>> -
>> -  e = XNEW (struct gdb_environ);
>> -
>> -  e->allocated = 10;
>> -  e->vector = (char **) xmalloc ((e->allocated + 1) * sizeof (char 
>> *));
>> -  e->vector[0] = 0;
>> -  return e;
>> -}
>> -
>> -/* Free an environment and all the strings in it.  */
>> -
>> -void
>> -free_environ (struct gdb_environ *e)
>> -{
>> -  char **vector = e->vector;
>> -
>> -  while (*vector)
>> -    xfree (*vector++);
>> -
>> -  xfree (e->vector);
>> -  xfree (e);
>> +  clear ();
>> +  m_environ_vector = std::move (e.m_environ_vector);
>> +  return *this;
>>  }
> 
> I should probably do an m_environ_vector.clear () before doing the
> std::move, because the vector will contain the NULL element.  I'll make
> sure to do this before I push the patch.

 From what I saw stepping in the STL code, the previous content of the 
moved-to vector is cleared, as if you called .clear().  In 
_M_move_assign, it moves the old content to a temporary, stack allocated 
vector, which gets cleared when exiting that function.  So I think it's 
not necessary.

Simon

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v5] C++ify gdb/common/environ.c
  2017-06-17  8:54   ` Simon Marchi
  2017-06-19  4:19     ` Sergio Durigan Junior
@ 2017-06-19 12:13     ` Pedro Alves
  2017-06-20 14:02       ` Pedro Alves
  1 sibling, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2017-06-19 12:13 UTC (permalink / raw)
  To: Simon Marchi, Sergio Durigan Junior; +Cc: GDB Patches

On 06/17/2017 09:54 AM, Simon Marchi wrote:

> I actually preferred the option of adding the NULL element to the vector
> in the gdb_environ constructor, since it allows always having the vector
> in a consistent state.  I don't think that avoiding that heap allocation
> is worth the complexity it adds to the code (unless we can prove
> otherwise by memory usage profiling).

I'm not exactly sure what complexity this is, but I'm not going to
strongly object to always putting in the NULL element, since that's
what we currently do today.  This shows we're missing unit test coverage
at least.

I was going to write something longish about premature pessimization,
and on how that in my experience is really a mindset that ends up causing
us to leave a bunch of easy optimizations that all adding up, do matter
significantly, coming from actually running perf against gdb and seeing
the sometimes quite silly hot spots that could have been easily
avoided.  However, I found this article, and I find that it mirrors
perfectly my view, and is much better written than what I was going to say:

 http://www.bornsleepy.com/2014/10/27/premature-pessimization-and-the-like.html

I fully subscribe to the above.

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v5] C++ify gdb/common/environ.c
  2017-06-19  4:19     ` Sergio Durigan Junior
@ 2017-06-19 13:40       ` Pedro Alves
  2017-06-19 16:19         ` Sergio Durigan Junior
  0 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2017-06-19 13:40 UTC (permalink / raw)
  To: Sergio Durigan Junior, Simon Marchi; +Cc: GDB Patches


On 06/19/2017 05:19 AM, Sergio Durigan Junior wrote:

> I
> think it also makes a lot more sense to always initialize the vector
> with a NULL element, because this means we're correctly dealing with the
> case where there's no environment variable to be passed to the inferior.

This sentence confuses me, because I don't understand what you mean
by "correctly" here.  What was incorrect?

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v6] C++ify gdb/common/environ.c
  2017-06-19  7:18     ` Simon Marchi
@ 2017-06-19 14:26       ` Pedro Alves
  2017-06-19 15:30         ` Simon Marchi
  0 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2017-06-19 14:26 UTC (permalink / raw)
  To: Simon Marchi, Sergio Durigan Junior; +Cc: GDB Patches


On 06/19/2017 08:18 AM, Simon Marchi wrote:
> On 2017-06-19 06:51, Sergio Durigan Junior wrote:
>> On Monday, June 19 2017, I wrote:
>>
>>> [...]
>>> -struct gdb_environ *
>>> -make_environ (void)
>>> +gdb_environ &
>>> +gdb_environ::operator= (gdb_environ &&e)
>>>  {
>>> -  struct gdb_environ *e;
>>> -
>>> -  e = XNEW (struct gdb_environ);
>>> -
>>> -  e->allocated = 10;
>>> -  e->vector = (char **) xmalloc ((e->allocated + 1) * sizeof (char *));
>>> -  e->vector[0] = 0;
>>> -  return e;
>>> -}
>>> -
>>> -/* Free an environment and all the strings in it.  */
>>> -
>>> -void
>>> -free_environ (struct gdb_environ *e)
>>> -{
>>> -  char **vector = e->vector;
>>> -
>>> -  while (*vector)
>>> -    xfree (*vector++);
>>> -
>>> -  xfree (e->vector);
>>> -  xfree (e);
>>> +  clear ();
>>> +  m_environ_vector = std::move (e.m_environ_vector);
>>> +  return *this;
>>>  }
>>
>> I should probably do an m_environ_vector.clear () before doing the
>> std::move, because the vector will contain the NULL element.  I'll make
>> sure to do this before I push the patch.
> 
> From what I saw stepping in the STL code, the previous content of the
> moved-to vector is cleared, as if you called .clear().  In
> _M_move_assign, it moves the old content to a temporary, stack allocated
> vector, which gets cleared when exiting that function.  So I think it's
> not necessary.

Right, m_environ_vector.clear() is not necessary.

Note that this move assignment (and likewise the move ctor) leaves the
source vector empty, which violates the "there's always a NULL entry
at the end" invariant.  That's OK if the only thing we want to support
of moved-from gdb_environ objects is destroying them, but please do
document that.

Otherwise, people assuming the standard library's rule, may be
confused/surprised, into thinking that this, e.g., should work:

gdb_environ env1;
env1.set ("VAR1", "value1");
gdb_environ env2;
env2 = std::move (env1);    // env1 has no NULL terminator after this.
env1.set ("VAR1", "value2); // whoops.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17.6.5.15 Moved-from state of library types                                         [lib.types.movedfrom]

    Objects of types defined in the C++ standard library may be moved from (12.8).
    Move operations may be explicitly specified or implicitly generated. Unless
    otherwise specified, such moved-from objects shall be placed in a valid
    but unspecified state.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v6] C++ify gdb/common/environ.c
  2017-06-19  4:36 ` [PATCH v6] " Sergio Durigan Junior
  2017-06-19  4:51   ` Sergio Durigan Junior
@ 2017-06-19 14:26   ` Pedro Alves
  2017-06-19 16:13     ` Sergio Durigan Junior
  1 sibling, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2017-06-19 14:26 UTC (permalink / raw)
  To: Sergio Durigan Junior, GDB Patches; +Cc: Simon Marchi

On 06/19/2017 05:35 AM, Sergio Durigan Junior wrote:
> +private:
> +  /* A vector containing the environment variables.  This is useful
> +     for when we need to obtain a 'char **' with all the existing
> +     variables.  */
> +  std::vector<char *> m_environ_vector;
> +};

This "This is useful" comment doesn't seem to make much
sense here in isolation.  What exactly is useful, and in comparison
to what else?  Maybe you're referring to the choice of type of element
in the vector, say vs a unique_ptr.  Please clarify the comment.  As
is, it would sound like a comment more fit to the class'es intro
or to the envp() method.

On 06/19/2017 05:35 AM, Sergio Durigan Junior wrote:
>    else
>      {
> -      char **vector = environ_vector (current_inferior ()->environment);
> +      char **envp = current_inferior ()->environment.envp ();
>  
> -      while (*vector)
> -	{
> -	  puts_filtered (*vector++);
> -	  puts_filtered ("\n");
> -	}
> +      if (envp != NULL)

I suspect this NULL check here was only needed in the previous
version that mishandled empty environs.  I can't see how it
makes sense now.  If you still need it, then there's a bug
elsewhere.

> +	for (int idx = 0; envp[idx] != NULL; ++idx)
> +	  {
> +	    puts_filtered (envp[idx]);
> +	    puts_filtered ("\n");
> +	  }
>      }



> +  if (setenv ("GDB_SELFTEST_ENVIRON", "1", 1) != 0)
> +    error ("Could not set environment variable for testing.");

Missing _() around error's format string.




> +
> +  gdb_environ env;
> +
> +  SELF_CHECK (env.envp ()[0] == NULL);
> +
> +  SELF_CHECK (env.get ("PWD") == NULL);
> +  env.set ("PWD", "test");
> +  env.unset ("PWD");
> +

Please add another

  SELF_CHECK (env.envp ()[0] == NULL);

after the unset.  I didn't spot any check making sure
that invariant holds after an unset.


Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v6] C++ify gdb/common/environ.c
  2017-06-19 14:26       ` Pedro Alves
@ 2017-06-19 15:30         ` Simon Marchi
  2017-06-19 15:44           ` Pedro Alves
  0 siblings, 1 reply; 47+ messages in thread
From: Simon Marchi @ 2017-06-19 15:30 UTC (permalink / raw)
  To: Pedro Alves; +Cc: Sergio Durigan Junior, GDB Patches

On 2017-06-19 16:26, Pedro Alves wrote:
> Right, m_environ_vector.clear() is not necessary.
> 
> Note that this move assignment (and likewise the move ctor) leaves the
> source vector empty, which violates the "there's always a NULL entry
> at the end" invariant.  That's OK if the only thing we want to support
> of moved-from gdb_environ objects is destroying them, but please do
> document that.
> 
> Otherwise, people assuming the standard library's rule, may be
> confused/surprised, into thinking that this, e.g., should work:
> 
> gdb_environ env1;
> env1.set ("VAR1", "value1");
> gdb_environ env2;
> env2 = std::move (env1);    // env1 has no NULL terminator after this.
> env1.set ("VAR1", "value2); // whoops.
> 
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> 17.6.5.15 Moved-from state of library types
>              [lib.types.movedfrom]
> 
>     Objects of types defined in the C++ standard library may be moved
> from (12.8).
>     Move operations may be explicitly specified or implicitly 
> generated. Unless
>     otherwise specified, such moved-from objects shall be placed in a 
> valid
>     but unspecified state.
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

That's a good point.  We should definitely not let the environ object 
get in an invalid state.

Whatever the rule we choose for the terminating NULL, there exists some 
valid vector states which result in invalid environ states.  For 
example, an environ whose vector contains { NULL, NULL } is not valid.  
Trying to set an env var in it would give { NULL, "FOO=BAR", NULL }, and 
that results in an unexpected environment array in the end.

Does that mean that after the vector move, we should make sure to leave 
the moved-from vector in a known state (i.e. clear it, and possible add 
a NULL), to make sure that we leave our environ object in a valid state?


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v6] C++ify gdb/common/environ.c
  2017-06-19 15:30         ` Simon Marchi
@ 2017-06-19 15:44           ` Pedro Alves
  2017-06-19 15:47             ` Pedro Alves
  2017-06-19 16:26             ` Simon Marchi
  0 siblings, 2 replies; 47+ messages in thread
From: Pedro Alves @ 2017-06-19 15:44 UTC (permalink / raw)
  To: Simon Marchi; +Cc: Sergio Durigan Junior, GDB Patches


On 06/19/2017 04:30 PM, Simon Marchi wrote:
> On 2017-06-19 16:26, Pedro Alves wrote:
>> Right, m_environ_vector.clear() is not necessary.
>>
>> Note that this move assignment (and likewise the move ctor) leaves the
>> source vector empty, which violates the "there's always a NULL entry
>> at the end" invariant.  That's OK if the only thing we want to support
>> of moved-from gdb_environ objects is destroying them, but please do
>> document that.
>>
>> Otherwise, people assuming the standard library's rule, may be
>> confused/surprised, into thinking that this, e.g., should work:
>>
>> gdb_environ env1;
>> env1.set ("VAR1", "value1");
>> gdb_environ env2;
>> env2 = std::move (env1);    // env1 has no NULL terminator after this.
>> env1.set ("VAR1", "value2); // whoops.
>>
>> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> 17.6.5.15 Moved-from state of library types
>>              [lib.types.movedfrom]
>>
>>     Objects of types defined in the C++ standard library may be moved
>> from (12.8).
>>     Move operations may be explicitly specified or implicitly
>> generated. Unless
>>     otherwise specified, such moved-from objects shall be placed in a
>> valid
>>     but unspecified state.
>> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> 
> That's a good point.  We should definitely not let the environ object
> get in an invalid state.
> 
> Whatever the rule we choose for the terminating NULL, there exists some
> valid vector states which result in invalid environ states.  For
> example, an environ whose vector contains { NULL, NULL } is not valid. 
> Trying to set an env var in it would give { NULL, "FOO=BAR", NULL }, and
> that results in an unexpected environment array in the end.
> 
> Does that mean that after the vector move, we should make sure to leave
> the moved-from vector in a known state (i.e. clear it, and possible add
> a NULL), to make sure that we leave our environ object in a valid state?

If we take the "always push a NULL on construction" approach, and
we want moved-from gdb_environs to be valid, then yes.  Note how this
results in extra heap allocations when e.g., returning a
gdb_environ from functions by value, and makes std::vector<gdb_environ>
much less efficient when it decides it needs to reallocate/move
elements.  Representing the empty state with a cleared internal
vector would avoid this.

Note BTW, that we need to be careful with self-move leaving the
*this object in a valid state.

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v6] C++ify gdb/common/environ.c
  2017-06-19 15:44           ` Pedro Alves
@ 2017-06-19 15:47             ` Pedro Alves
  2017-06-19 16:26             ` Simon Marchi
  1 sibling, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2017-06-19 15:47 UTC (permalink / raw)
  To: Simon Marchi; +Cc: Sergio Durigan Junior, GDB Patches

On 06/19/2017 04:44 PM, Pedro Alves wrote:

> Note how this
> results in extra heap allocations when e.g., returning a
> gdb_environ from functions by value,

Bah, unless RVO, or C++17 guaranteed copy-elision, of course.
This particular sentence here is a bit misleading, sorry.

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v6] C++ify gdb/common/environ.c
  2017-06-19 14:26   ` Pedro Alves
@ 2017-06-19 16:13     ` Sergio Durigan Junior
  2017-06-19 16:38       ` Pedro Alves
  0 siblings, 1 reply; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-06-19 16:13 UTC (permalink / raw)
  To: Pedro Alves; +Cc: GDB Patches, Simon Marchi

On Monday, June 19 2017, Pedro Alves wrote:

> On 06/19/2017 05:35 AM, Sergio Durigan Junior wrote:
>> +private:
>> +  /* A vector containing the environment variables.  This is useful
>> +     for when we need to obtain a 'char **' with all the existing
>> +     variables.  */
>> +  std::vector<char *> m_environ_vector;
>> +};
>
> This "This is useful" comment doesn't seem to make much
> sense here in isolation.  What exactly is useful, and in comparison
> to what else?  Maybe you're referring to the choice of type of element
> in the vector, say vs a unique_ptr.  Please clarify the comment.  As
> is, it would sound like a comment more fit to the class'es intro
> or to the envp() method.

This is probably a leftover comment from a very early version.  I
removed the part about usefulness.

> On 06/19/2017 05:35 AM, Sergio Durigan Junior wrote:
>>    else
>>      {
>> -      char **vector = environ_vector (current_inferior ()->environment);
>> +      char **envp = current_inferior ()->environment.envp ();
>>  
>> -      while (*vector)
>> -	{
>> -	  puts_filtered (*vector++);
>> -	  puts_filtered ("\n");
>> -	}
>> +      if (envp != NULL)
>
> I suspect this NULL check here was only needed in the previous
> version that mishandled empty environs.  I can't see how it
> makes sense now.  If you still need it, then there's a bug
> elsewhere.

No, it is not needed anymore.  Removed.

>> +	for (int idx = 0; envp[idx] != NULL; ++idx)
>> +	  {
>> +	    puts_filtered (envp[idx]);
>> +	    puts_filtered ("\n");
>> +	  }
>>      }
>
>
>
>> +  if (setenv ("GDB_SELFTEST_ENVIRON", "1", 1) != 0)
>> +    error ("Could not set environment variable for testing.");
>
> Missing _() around error's format string.

Fixed.

>> +
>> +  gdb_environ env;
>> +
>> +  SELF_CHECK (env.envp ()[0] == NULL);
>> +
>> +  SELF_CHECK (env.get ("PWD") == NULL);
>> +  env.set ("PWD", "test");
>> +  env.unset ("PWD");
>> +
>
> Please add another
>
>   SELF_CHECK (env.envp ()[0] == NULL);
>
> after the unset.  I didn't spot any check making sure
> that invariant holds after an unset.

This invariant is not supposed to hold after every unset, only after a
clear or after an unset that removes the only variable in the vector.

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v5] C++ify gdb/common/environ.c
  2017-06-19 13:40       ` Pedro Alves
@ 2017-06-19 16:19         ` Sergio Durigan Junior
  0 siblings, 0 replies; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-06-19 16:19 UTC (permalink / raw)
  To: Pedro Alves; +Cc: Simon Marchi, GDB Patches

On Monday, June 19 2017, Pedro Alves wrote:

> On 06/19/2017 05:19 AM, Sergio Durigan Junior wrote:
>
>> I
>> think it also makes a lot more sense to always initialize the vector
>> with a NULL element, because this means we're correctly dealing with the
>> case where there's no environment variable to be passed to the inferior.
>
> This sentence confuses me, because I don't understand what you mean
> by "correctly" here.  What was incorrect?

What I mean is that the internal state of the vector is correct from the
get-go.  As you said in another message, the environment vector passed
to the exec* family of syscalls should contain at least one element,
NULL.  This means we can make sure that the vector generated by the API
(via the envp method) obeys this rule, or that the internal vector
(m_environ_vector) obeys this rule.  I prefer the second method, because
it leaves everything (internal and external) in a correct state.

I don't have any advanced C++ arguments to base my opinion on, and I am
well aware that this means preallocating things that maybe didn't need
to be allocated, but I still prefer keeping the NULL element there
because it makes more sense to me.

My half-cent to this discussion.

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v6] C++ify gdb/common/environ.c
  2017-06-19 15:44           ` Pedro Alves
  2017-06-19 15:47             ` Pedro Alves
@ 2017-06-19 16:26             ` Simon Marchi
  2017-06-19 16:55               ` Pedro Alves
  1 sibling, 1 reply; 47+ messages in thread
From: Simon Marchi @ 2017-06-19 16:26 UTC (permalink / raw)
  To: Pedro Alves; +Cc: Sergio Durigan Junior, GDB Patches

On 2017-06-19 17:44, Pedro Alves wrote:
> If we take the "always push a NULL on construction" approach, and
> we want moved-from gdb_environs to be valid, then yes.  Note how this
> results in extra heap allocations when e.g., returning a
> gdb_environ from functions by value, and makes std::vector<gdb_environ>
> much less efficient when it decides it needs to reallocate/move
> elements.  Representing the empty state with a cleared internal
> vector would avoid this.

Given the move case, since the goal is to be efficient, then yeah I 
would agree
that it would make sense to make a little bit of efforts to avoid 
allocating
memory for an objects we are almost certainly throwing away.

But still, in order to leave environ objects in a valid state after a 
move and
to pedantically comply with the STL spec which says that the vector is 
left in
an unspecified state, shouldn't we do a .clear () on the moved-from 
vector after
the move?

> Note BTW, that we need to be careful with self-move leaving the
> *this object in a valid state.

Should we just do

if (&other == this)
   return *this;

?

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v6] C++ify gdb/common/environ.c
  2017-06-19 16:13     ` Sergio Durigan Junior
@ 2017-06-19 16:38       ` Pedro Alves
  2017-06-19 16:46         ` Sergio Durigan Junior
  0 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2017-06-19 16:38 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: GDB Patches, Simon Marchi


On 06/19/2017 05:13 PM, Sergio Durigan Junior wrote:
> On Monday, June 19 2017, Pedro Alves wrote:

>>> +
>>> +  gdb_environ env;
>>> +
>>> +  SELF_CHECK (env.envp ()[0] == NULL);
>>> +
>>> +  SELF_CHECK (env.get ("PWD") == NULL);
>>> +  env.set ("PWD", "test");
>>> +  env.unset ("PWD");
>>> +
>>
>> Please add another
>>
>>   SELF_CHECK (env.envp ()[0] == NULL);
>>
>> after the unset.  I didn't spot any check making sure
>> that invariant holds after an unset.
> 
> This invariant is not supposed to hold after every unset, only after a
> clear or after an unset that removes the only variable in the vector.

... which is exactly the case above.  And for unsets where there are
still elements, the invariant is that the last element is NULL
[which is of course the same invariant].  So maybe we should have a
little function like this (could reuse countargv too):

static size_t
countenvp (const gdb_environ &env)
{
   char **envp = env.envp ();
   size_t count = 0;
   while (envp[count] != NULL)
     count++;
   return count;
}

Used instead of the NULL SELF_CHECKs, like:

  gdb_environ env;

  /* This makes sure that env.envp() is NULL terminated.  */
  SELF_CHECK (countenvp (env) == 0);

  /* ENV is empty, so we shouldn't be able to find any var.  */
  SELF_CHECK (env.get ("PWD") == NULL);

  /* Set a var, and make sure that env.envp() is still NULL
     terminated.  */
  env.set ("PWD", "test");
  SELF_CHECK (countenvp (env) == 1);

  /* Clear the var and make sure that env.envp() is left NULL
     terminated when we remove the last element.  */
  env.unset ("PWD");
  SELF_CHECK (countenvp (env) == 0);

etc.

I find that adding guiding comments like above helps, btw.

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v6] C++ify gdb/common/environ.c
  2017-06-19 16:38       ` Pedro Alves
@ 2017-06-19 16:46         ` Sergio Durigan Junior
  0 siblings, 0 replies; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-06-19 16:46 UTC (permalink / raw)
  To: Pedro Alves; +Cc: GDB Patches, Simon Marchi

On Monday, June 19 2017, Pedro Alves wrote:

> On 06/19/2017 05:13 PM, Sergio Durigan Junior wrote:
>> On Monday, June 19 2017, Pedro Alves wrote:
>
>>>> +
>>>> +  gdb_environ env;
>>>> +
>>>> +  SELF_CHECK (env.envp ()[0] == NULL);
>>>> +
>>>> +  SELF_CHECK (env.get ("PWD") == NULL);
>>>> +  env.set ("PWD", "test");
>>>> +  env.unset ("PWD");
>>>> +
>>>
>>> Please add another
>>>
>>>   SELF_CHECK (env.envp ()[0] == NULL);
>>>
>>> after the unset.  I didn't spot any check making sure
>>> that invariant holds after an unset.
>> 
>> This invariant is not supposed to hold after every unset, only after a
>> clear or after an unset that removes the only variable in the vector.
>
> ... which is exactly the case above.  And for unsets where there are
> still elements, the invariant is that the last element is NULL
> [which is of course the same invariant].  So maybe we should have a
> little function like this (could reuse countargv too):

Yes, I know, I was just correcting the last part of your sentence:

  I didn't spot any check making sure that invariant holds after *an*
  unset.

(emphasis mine)

> static size_t
> countenvp (const gdb_environ &env)
> {
>    char **envp = env.envp ();
>    size_t count = 0;
>    while (envp[count] != NULL)
>      count++;
>    return count;
> }
>
> Used instead of the NULL SELF_CHECKs, like:
>
>   gdb_environ env;
>
>   /* This makes sure that env.envp() is NULL terminated.  */
>   SELF_CHECK (countenvp (env) == 0);
>
>   /* ENV is empty, so we shouldn't be able to find any var.  */
>   SELF_CHECK (env.get ("PWD") == NULL);
>
>   /* Set a var, and make sure that env.envp() is still NULL
>      terminated.  */
>   env.set ("PWD", "test");
>   SELF_CHECK (countenvp (env) == 1);
>
>   /* Clear the var and make sure that env.envp() is left NULL
>      terminated when we remove the last element.  */
>   env.unset ("PWD");
>   SELF_CHECK (countenvp (env) == 0);
>
> etc.
>
> I find that adding guiding comments like above helps, btw.

Right, I'll add guiding comments.

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v6] C++ify gdb/common/environ.c
  2017-06-19 16:26             ` Simon Marchi
@ 2017-06-19 16:55               ` Pedro Alves
  2017-06-19 17:59                 ` Sergio Durigan Junior
  0 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2017-06-19 16:55 UTC (permalink / raw)
  To: Simon Marchi; +Cc: Sergio Durigan Junior, GDB Patches


On 06/19/2017 05:26 PM, Simon Marchi wrote:
> On 2017-06-19 17:44, Pedro Alves wrote:
>> If we take the "always push a NULL on construction" approach, and
>> we want moved-from gdb_environs to be valid, then yes.  Note how this
>> results in extra heap allocations when e.g., returning a
>> gdb_environ from functions by value, and makes std::vector<gdb_environ>
>> much less efficient when it decides it needs to reallocate/move
>> elements.  Representing the empty state with a cleared internal
>> vector would avoid this.
> 
> Given the move case, since the goal is to be efficient, then yeah I
> would agree
> that it would make sense to make a little bit of efforts to avoid
> allocating
> memory for an objects we are almost certainly throwing away.
> 
> But still, in order to leave environ objects in a valid state after a
> move and
> to pedantically comply with the STL spec which says that the vector is
> left in
> an unspecified state, shouldn't we do a .clear () on the moved-from
> vector after
> the move?

See accepted answer at:

 https://stackoverflow.com/questions/17730689/is-a-moved-from-vector-always-empty

So the only case where it'd be needed would be in op=, and iff the 
vectors had different allocators, which is not the case here.
So no, it's not necessary.  But I'd be fine with calling it.

> 
>> Note BTW, that we need to be careful with self-move leaving the
>> *this object in a valid state.
> 
> Should we just do
> 
> if (&other == this)
>   return *this;

Might not be necessary if without that the object ends up
valid anyway.  But what you wrote is a safe bet.

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v6] C++ify gdb/common/environ.c
  2017-06-19 16:55               ` Pedro Alves
@ 2017-06-19 17:59                 ` Sergio Durigan Junior
  2017-06-19 18:09                   ` Pedro Alves
  0 siblings, 1 reply; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-06-19 17:59 UTC (permalink / raw)
  To: Pedro Alves; +Cc: Simon Marchi, GDB Patches

On Monday, June 19 2017, Pedro Alves wrote:

> On 06/19/2017 05:26 PM, Simon Marchi wrote:
>> On 2017-06-19 17:44, Pedro Alves wrote:
>>> If we take the "always push a NULL on construction" approach, and
>>> we want moved-from gdb_environs to be valid, then yes.  Note how this
>>> results in extra heap allocations when e.g., returning a
>>> gdb_environ from functions by value, and makes std::vector<gdb_environ>
>>> much less efficient when it decides it needs to reallocate/move
>>> elements.  Representing the empty state with a cleared internal
>>> vector would avoid this.
>> 
>> Given the move case, since the goal is to be efficient, then yeah I
>> would agree
>> that it would make sense to make a little bit of efforts to avoid
>> allocating
>> memory for an objects we are almost certainly throwing away.
>> 
>> But still, in order to leave environ objects in a valid state after a
>> move and
>> to pedantically comply with the STL spec which says that the vector is
>> left in
>> an unspecified state, shouldn't we do a .clear () on the moved-from
>> vector after
>> the move?
>
> See accepted answer at:
>
>  https://stackoverflow.com/questions/17730689/is-a-moved-from-vector-always-empty
>
> So the only case where it'd be needed would be in op=, and iff the 
> vectors had different allocators, which is not the case here.
> So no, it's not necessary.  But I'd be fine with calling it.
>
>> 
>>> Note BTW, that we need to be careful with self-move leaving the
>>> *this object in a valid state.
>> 
>> Should we just do
>> 
>> if (&other == this)
>>   return *this;
>
> Might not be necessary if without that the object ends up
> valid anyway.  But what you wrote is a safe bet.

So, what do you guys think about the patch below, which applies on top
of the original?

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

diff --git a/gdb/common/environ.c b/gdb/common/environ.c
index 09860c9..944276c 100644
--- a/gdb/common/environ.c
+++ b/gdb/common/environ.c
@@ -25,13 +25,18 @@
 gdb_environ &
 gdb_environ::operator= (gdb_environ &&e)
 {
-  clear ();
+  if (&e == this)
+    return *this;
+
   m_environ_vector = std::move (e.m_environ_vector);
+  e.m_environ_vector.clear ();
+  e.m_environ_vector.push_back (NULL);
   return *this;
 }
 
 /* Create a gdb_environ object using the host's environment
    variables.  */
+
 gdb_environ gdb_environ::from_host_environ ()
 {
   extern char **environ;
diff --git a/gdb/common/environ.h b/gdb/common/environ.h
index b882bc7..054813c 100644
--- a/gdb/common/environ.h
+++ b/gdb/common/environ.h
@@ -67,9 +67,7 @@ public:
   char **envp () const;
 
 private:
-  /* A vector containing the environment variables.  This is useful
-     for when we need to obtain a 'char **' with all the existing
-     variables.  */
+  /* A vector containing the environment variables.  */
   std::vector<char *> m_environ_vector;
 };
 
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index ee0754d..defa7b0 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -2151,12 +2151,11 @@ environment_info (char *var, int from_tty)
     {
       char **envp = current_inferior ()->environment.envp ();
 
-      if (envp != NULL)
-	for (int idx = 0; envp[idx] != NULL; ++idx)
-	  {
-	    puts_filtered (envp[idx]);
-	    puts_filtered ("\n");
-	  }
+      for (int idx = 0; envp[idx] != NULL; ++idx)
+	{
+	  puts_filtered (envp[idx]);
+	  puts_filtered ("\n");
+	}
     }
 }
 
diff --git a/gdb/unittests/environ-selftests.c b/gdb/unittests/environ-selftests.c
index 0bcc6cc..8670d58 100644
--- a/gdb/unittests/environ-selftests.c
+++ b/gdb/unittests/environ-selftests.c
@@ -27,36 +27,58 @@ namespace gdb_environ_tests {
 static void
 run_tests ()
 {
+  /* Set a test environment variable.  This will be unset at the end
+     of this function.  */
   if (setenv ("GDB_SELFTEST_ENVIRON", "1", 1) != 0)
-    error ("Could not set environment variable for testing.");
+    error (_("Could not set environment variable for testing."));
 
   gdb_environ env;
 
+  /* When the vector is initialized, there should always be one NULL
+     element in it.  */
   SELF_CHECK (env.envp ()[0] == NULL);
 
+  /* Make sure that there is no other element.  */
   SELF_CHECK (env.get ("PWD") == NULL);
+
+  /* Check if unset followed by a set in an empty vector works.  */
   env.set ("PWD", "test");
+  SELF_CHECK (strcmp (env.get ("PWD"), "test") == 0);
+  /* The second element must be NULL.  */
+  SELF_CHECK (env.envp ()[1] == NULL);
   env.unset ("PWD");
+  SELF_CHECK (env.envp ()[0] == NULL);
 
+  /* Initialize the environment vector using the host's environ.  */
   env = gdb_environ::from_host_environ ();
 
+  /* Our test environment variable should be present at the
+     vector.  */
   SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0);
 
+  /* Set our test variable to another value.  */
   env.set ("GDB_SELFTEST_ENVIRON", "test");
   SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "test") == 0);
 
+  /* And unset our test variable.  The variable still exists in the
+     host's environment, but doesn't exist in our vector.  */
   env.unset ("GDB_SELFTEST_ENVIRON");
   SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL);
 
+  /* Re-set the test variable.  */
   env.set ("GDB_SELFTEST_ENVIRON", "1");
   SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0);
 
+  /* When we clear our environ vector, there should be only one
+     element on it (NULL), and we shouldn't be able to get our test
+     variable.  */
   env.clear ();
-
   SELF_CHECK (env.envp ()[0] == NULL);
-
   SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL);
 
+  /* Reinitialize our environ vector using the host environ.  We
+     should be able to see one (and only one) instance of the test
+     variable.  */
   env = gdb_environ::from_host_environ ();
   char **envp = env.envp ();
   int num_found = 0;
@@ -64,11 +86,14 @@ run_tests ()
   for (size_t i = 0; envp[i] != NULL; ++i)
     if (strcmp (envp[i], "GDB_SELFTEST_ENVIRON=1") == 0)
       ++num_found;
-
   SELF_CHECK (num_found == 1);
 
+  /* Get rid of our test variable.  */
   unsetenv ("GDB_SELFTEST_ENVIRON");
 
+  /* Test the case when we set a variable A, then set a variable B,
+     then unset A, and make sure that we cannot find A in the environ
+     vector, but can still find B.  */
   env.set ("GDB_SELFTEST_ENVIRON_1", "aaa");
   SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON_1"), "aaa") == 0);
 
@@ -78,6 +103,21 @@ run_tests ()
   env.unset ("GDB_SELFTEST_ENVIRON_1");
   SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON_1") == NULL);
   SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON_2"), "bbb") == 0);
+
+  env.clear ();
+
+  /* Test that after a std::move the moved-from object is left at a
+     valid state (i.e., its only element is NULL).  */
+  env.set ("A", "1");
+  SELF_CHECK (strcmp (env.get ("A"), "1") == 0);
+  gdb_environ env2;
+  env2 = std::move (env);
+  SELF_CHECK (env.envp ()[0] == NULL);
+  SELF_CHECK (strcmp (env2.get ("A"), "1") == 0);
+  SELF_CHECK (env2.envp ()[1] == NULL);
+  env.set ("B", "2");
+  SELF_CHECK (strcmp (env.get ("B"), "2") == 0);
+  SELF_CHECK (env.envp ()[1] == NULL);
 }
 } /* namespace gdb_environ */
 } /* namespace selftests */

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v6] C++ify gdb/common/environ.c
  2017-06-19 17:59                 ` Sergio Durigan Junior
@ 2017-06-19 18:09                   ` Pedro Alves
  2017-06-19 18:23                     ` Sergio Durigan Junior
  0 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2017-06-19 18:09 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: Simon Marchi, GDB Patches


On 06/19/2017 06:59 PM, Sergio Durigan Junior wrote:
> On Monday, June 19 2017, Pedro Alves wrote:
> 
>> On 06/19/2017 05:26 PM, Simon Marchi wrote:
>>> On 2017-06-19 17:44, Pedro Alves wrote:
>>>> If we take the "always push a NULL on construction" approach, and
>>>> we want moved-from gdb_environs to be valid, then yes.  Note how this
>>>> results in extra heap allocations when e.g., returning a
>>>> gdb_environ from functions by value, and makes std::vector<gdb_environ>
>>>> much less efficient when it decides it needs to reallocate/move
>>>> elements.  Representing the empty state with a cleared internal
>>>> vector would avoid this.
>>>
>>> Given the move case, since the goal is to be efficient, then yeah I
>>> would agree
>>> that it would make sense to make a little bit of efforts to avoid
>>> allocating
>>> memory for an objects we are almost certainly throwing away.
>>>
>>> But still, in order to leave environ objects in a valid state after a
>>> move and
>>> to pedantically comply with the STL spec which says that the vector is
>>> left in
>>> an unspecified state, shouldn't we do a .clear () on the moved-from
>>> vector after
>>> the move?
>>
>> See accepted answer at:
>>
>>  https://stackoverflow.com/questions/17730689/is-a-moved-from-vector-always-empty
>>
>> So the only case where it'd be needed would be in op=, and iff the 
>> vectors had different allocators, which is not the case here.
>> So no, it's not necessary.  But I'd be fine with calling it.
>>
>>>
>>>> Note BTW, that we need to be careful with self-move leaving the
>>>> *this object in a valid state.
>>>
>>> Should we just do
>>>
>>> if (&other == this)
>>>   return *this;
>>
>> Might not be necessary if without that the object ends up
>> valid anyway.  But what you wrote is a safe bet.
> 
> So, what do you guys think about the patch below, which applies on top
> of the original?

Missed fixing move ctor?

+  /* Move constructor.  */
+  gdb_environ (gdb_environ &&e)
+    : m_environ_vector (std::move (e.m_environ_vector))
+  {}

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v6] C++ify gdb/common/environ.c
  2017-06-19 18:09                   ` Pedro Alves
@ 2017-06-19 18:23                     ` Sergio Durigan Junior
  2017-06-19 18:36                       ` Pedro Alves
  0 siblings, 1 reply; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-06-19 18:23 UTC (permalink / raw)
  To: Pedro Alves; +Cc: Simon Marchi, GDB Patches

On Monday, June 19 2017, Pedro Alves wrote:

> On 06/19/2017 06:59 PM, Sergio Durigan Junior wrote:
>> On Monday, June 19 2017, Pedro Alves wrote:
>> 
>>> On 06/19/2017 05:26 PM, Simon Marchi wrote:
>>>> On 2017-06-19 17:44, Pedro Alves wrote:
>>>>> If we take the "always push a NULL on construction" approach, and
>>>>> we want moved-from gdb_environs to be valid, then yes.  Note how this
>>>>> results in extra heap allocations when e.g., returning a
>>>>> gdb_environ from functions by value, and makes std::vector<gdb_environ>
>>>>> much less efficient when it decides it needs to reallocate/move
>>>>> elements.  Representing the empty state with a cleared internal
>>>>> vector would avoid this.
>>>>
>>>> Given the move case, since the goal is to be efficient, then yeah I
>>>> would agree
>>>> that it would make sense to make a little bit of efforts to avoid
>>>> allocating
>>>> memory for an objects we are almost certainly throwing away.
>>>>
>>>> But still, in order to leave environ objects in a valid state after a
>>>> move and
>>>> to pedantically comply with the STL spec which says that the vector is
>>>> left in
>>>> an unspecified state, shouldn't we do a .clear () on the moved-from
>>>> vector after
>>>> the move?
>>>
>>> See accepted answer at:
>>>
>>>  https://stackoverflow.com/questions/17730689/is-a-moved-from-vector-always-empty
>>>
>>> So the only case where it'd be needed would be in op=, and iff the 
>>> vectors had different allocators, which is not the case here.
>>> So no, it's not necessary.  But I'd be fine with calling it.
>>>
>>>>
>>>>> Note BTW, that we need to be careful with self-move leaving the
>>>>> *this object in a valid state.
>>>>
>>>> Should we just do
>>>>
>>>> if (&other == this)
>>>>   return *this;
>>>
>>> Might not be necessary if without that the object ends up
>>> valid anyway.  But what you wrote is a safe bet.
>> 
>> So, what do you guys think about the patch below, which applies on top
>> of the original?
>
> Missed fixing move ctor?
>
> +  /* Move constructor.  */
> +  gdb_environ (gdb_environ &&e)
> +    : m_environ_vector (std::move (e.m_environ_vector))
> +  {}

Indeed.  Fixed now.  I'll submit v7.

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 47+ messages in thread

* [PATCH v7] C++ify gdb/common/environ.c
  2017-04-13  4:05 [PATCH] C++ify gdb/common/environ.c Sergio Durigan Junior
                   ` (4 preceding siblings ...)
  2017-06-19  4:36 ` [PATCH v6] " Sergio Durigan Junior
@ 2017-06-19 18:27 ` Sergio Durigan Junior
  2017-06-20  3:27 ` [PATCH v8] " Sergio Durigan Junior
  6 siblings, 0 replies; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-06-19 18:27 UTC (permalink / raw)
  To: GDB Patches; +Cc: Simon Marchi, Pedro Alves, Sergio Durigan Junior

Changed from v6:

- The internal state of m_environ_vector is always correct, i.e., the
  vector always contains at least one NULL element in it.

- Expanded selftests in order to check for special situations related
  to std::move; also included guiding comments explaining what is
  being tested.

- Fix minor nits.

As part of the preparation necessary for my upcoming task, I'd like to
propose that we turn gdb_environ into a class.  The approach taken
here is simple: the class gdb_environ contains everything that is
needed to manipulate the environment variables.  These variables are
stored in an std::vector<char *>, which can be converted to a 'char
**' and passed as argument to functions that need it.

The usage has not changed much.  As per Pedro's suggestion, this class
uses a static factory method initialization.  This means that when an
instance is created, it is initially empty.  When needed, it has to be
initialized using the static method 'from_host_environ'.

As mentioned before, this is a preparation for an upcoming work that I
will be posting in the next few weeks or so.  For that work, I'll
probably create another data structure that will contain all the
environment variables that were set by the user using the 'set
environment' command, because I'll need access to them.  This will be
much easier with the class-ification of gdb_environ.

As noted, this has been regression-tested with the new version of
environ.exp and no regressions were found.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	* Makefile.in (SUBDIR_UNITTESTS_SRCS): Add
	'unittests/environ-selftests.c'.
	(SUBDIR_UNITTESTS_OBS): Add 'environ-selftests.o'.
	* charset.c (find_charset_names): Declare object 'iconv_env'.
	Update code to use 'iconv_env' object.  Remove call to
	'free_environ'.
	* common/environ.c: Include <utility>.
	(make_environ): Delete function.
	(free_environ): Delete function.
	(gdb_environ::clear): New function.
	(gdb_environ::operator=): New function.
	(gdb_environ::get): Likewise.
	(environ_vector): Delete function.
	(set_in_environ): Delete function.
	(gdb_environ::set): New function.
	(unset_in_environ): Delete function.
	(gdb_environ::unset): New function.
	(gdb_environ::envp): Likewise.
	* common/environ.h: Include <vector>.
	(struct gdb_environ): Delete; transform into...
	(class gdb_environ): ... this class.
	(free_environ): Delete prototype.
	(init_environ, get_in_environ, set_in_environ, unset_in_environ,
	environ_vector): Likewise.
	* infcmd.c (run_command_1): Update code to call
	'envp' from 'gdb_environ' class.
	(environment_info): Update code to call methods from 'gdb_environ'
	class.
	(unset_environment_command): Likewise.
	(path_info): Likewise.
	(path_command): Likewise.
	* inferior.c (inferior::~inferior): Delete call to 'free_environ'.
	(inferior::inferior): Initialize 'environment' using the host's
	information.
	* inferior.h: Remove forward declaration of 'struct gdb_environ'.
	Include "environ.h".
	(class inferior) <environment>: Change type from 'struct
	gdb_environ' to 'gdb_environ'.
	* mi/mi-cmd-env.c (mi_cmd_env_path): Update code to call
	methods from 'gdb_environ' class.
	* solib.c (solib_find_1): Likewise
	* unittests/environ-selftests.c: New file.

gdb/gdbserver/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	* linux-low.c (linux_create_inferior): Adjust code to access the
	environment information via 'gdb_environ' class.
	* lynx-low.c (lynx_create_inferior): Likewise.
	* server.c (our_environ): Make it an instance of 'gdb_environ'.
	(get_environ): Return a pointer to 'our_environ'.
	(captured_main): Initialize 'our_environ'.
	* server.h (get_environ): Adjust prototype.
	* spu-low.c (spu_create_inferior): Adjust code to access the
	environment information via 'gdb_environ' class.
---
 gdb/Makefile.in                   |   2 +
 gdb/charset.c                     |  11 +--
 gdb/common/environ.c              | 200 +++++++++++++++-----------------------
 gdb/common/environ.h              |  66 +++++++++----
 gdb/gdbserver/linux-low.c         |   2 +-
 gdb/gdbserver/lynx-low.c          |   2 +-
 gdb/gdbserver/server.c            |   9 +-
 gdb/gdbserver/server.h            |   6 +-
 gdb/gdbserver/spu-low.c           |   2 +-
 gdb/infcmd.c                      |  28 +++---
 gdb/inferior.c                    |   4 +-
 gdb/inferior.h                    |   6 +-
 gdb/mi/mi-cmd-env.c               |   6 +-
 gdb/solib.c                       |   7 +-
 gdb/unittests/environ-selftests.c | 129 ++++++++++++++++++++++++
 15 files changed, 292 insertions(+), 188 deletions(-)
 create mode 100644 gdb/unittests/environ-selftests.c

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 153a5dd..b27f698 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -526,6 +526,7 @@ SUBDIR_PYTHON_LDFLAGS =
 SUBDIR_PYTHON_CFLAGS =
 
 SUBDIR_UNITTESTS_SRCS = \
+	unittests/environ-selftests.c \
 	unittests/function-view-selftests.c \
 	unittests/offset-type-selftests.c \
 	unittests/optional-selftests.c \
@@ -533,6 +534,7 @@ SUBDIR_UNITTESTS_SRCS = \
 	unittests/scoped_restore-selftests.c
 
 SUBDIR_UNITTESTS_OBS = \
+	environ-selftests.o \
 	function-view-selftests.o \
 	offset-type-selftests.o \
 	optional-selftests.o \
diff --git a/gdb/charset.c b/gdb/charset.c
index dbe46a4..be95bbe 100644
--- a/gdb/charset.c
+++ b/gdb/charset.c
@@ -794,16 +794,14 @@ find_charset_names (void)
   int err, status;
   int fail = 1;
   int flags;
-  struct gdb_environ *iconv_env;
+  gdb_environ iconv_env = gdb_environ::from_host_environ ();
   char *iconv_program;
 
   /* Older iconvs, e.g. 2.2.2, don't omit the intro text if stdout is
      not a tty.  We need to recognize it and ignore it.  This text is
      subject to translation, so force LANGUAGE=C.  */
-  iconv_env = make_environ ();
-  init_environ (iconv_env);
-  set_in_environ (iconv_env, "LANGUAGE", "C");
-  set_in_environ (iconv_env, "LC_ALL", "C");
+  iconv_env.set ("LANGUAGE", "C");
+  iconv_env.set ("LC_ALL", "C");
 
   child = pex_init (PEX_USE_PIPES, "iconv", NULL);
 
@@ -827,7 +825,7 @@ find_charset_names (void)
   /* Note that we simply ignore errors here.  */
   if (!pex_run_in_environment (child, flags,
 			       args[0], const_cast<char **> (args),
-			       environ_vector (iconv_env),
+			       iconv_env.envp (),
 			       NULL, NULL, &err))
     {
       FILE *in = pex_read_output (child, 0);
@@ -901,7 +899,6 @@ find_charset_names (void)
 
   xfree (iconv_program);
   pex_free (child);
-  free_environ (iconv_env);
 
   if (fail)
     {
diff --git a/gdb/common/environ.c b/gdb/common/environ.c
index 3145d01..944276c 100644
--- a/gdb/common/environ.c
+++ b/gdb/common/environ.c
@@ -18,165 +18,119 @@
 #include "common-defs.h"
 #include "environ.h"
 #include <algorithm>
-\f
+#include <utility>
 
-/* Return a new environment object.  */
+/* See common/environ.h.  */
 
-struct gdb_environ *
-make_environ (void)
+gdb_environ &
+gdb_environ::operator= (gdb_environ &&e)
 {
-  struct gdb_environ *e;
+  if (&e == this)
+    return *this;
 
-  e = XNEW (struct gdb_environ);
-
-  e->allocated = 10;
-  e->vector = (char **) xmalloc ((e->allocated + 1) * sizeof (char *));
-  e->vector[0] = 0;
-  return e;
+  m_environ_vector = std::move (e.m_environ_vector);
+  e.m_environ_vector.clear ();
+  e.m_environ_vector.push_back (NULL);
+  return *this;
 }
 
-/* Free an environment and all the strings in it.  */
+/* Create a gdb_environ object using the host's environment
+   variables.  */
 
-void
-free_environ (struct gdb_environ *e)
-{
-  char **vector = e->vector;
-
-  while (*vector)
-    xfree (*vector++);
-
-  xfree (e->vector);
-  xfree (e);
-}
-
-/* Copy the environment given to this process into E.
-   Also copies all the strings in it, so we can be sure
-   that all strings in these environments are safe to free.  */
-
-void
-init_environ (struct gdb_environ *e)
+gdb_environ gdb_environ::from_host_environ ()
 {
   extern char **environ;
-  int i;
+  gdb_environ e;
 
   if (environ == NULL)
-    return;
+    return e;
 
-  for (i = 0; environ[i]; i++) /*EMPTY */ ;
-
-  if (e->allocated < i)
+  for (int i = 0; environ[i] != NULL; ++i)
     {
-      e->allocated = std::max (i, e->allocated + 10);
-      e->vector = (char **) xrealloc ((char *) e->vector,
-				      (e->allocated + 1) * sizeof (char *));
+      /* Make sure we add the element before the last (NULL).  */
+      e.m_environ_vector.insert (e.m_environ_vector.end () - 1,
+				 xstrdup (environ[i]));
     }
 
-  memcpy (e->vector, environ, (i + 1) * sizeof (char *));
+  return e;
+}
 
-  while (--i >= 0)
-    {
-      int len = strlen (e->vector[i]);
-      char *newobj = (char *) xmalloc (len + 1);
+/* See common/environ.h.  */
 
-      memcpy (newobj, e->vector[i], len + 1);
-      e->vector[i] = newobj;
-    }
+void
+gdb_environ::clear ()
+{
+  for (char *v : m_environ_vector)
+    xfree (v);
+  m_environ_vector.clear ();
+  /* Always add the NULL element.  */
+  m_environ_vector.push_back (NULL);
 }
 
-/* Return the vector of environment E.
-   This is used to get something to pass to execve.  */
+/* Helper function to check if STRING contains an environment variable
+   assignment of VAR, i.e., if STRING starts with 'VAR='.  Return true
+   if it contains, false otherwise.  */
 
-char **
-environ_vector (struct gdb_environ *e)
+static bool
+match_var_in_string (char *string, const char *var, size_t var_len)
 {
-  return e->vector;
+  if (strncmp (string, var, var_len) == 0 && string[var_len] == '=')
+    return true;
+
+  return false;
 }
-\f
-/* Return the value in environment E of variable VAR.  */
 
-char *
-get_in_environ (const struct gdb_environ *e, const char *var)
+/* See common/environ.h.  */
+
+const char *
+gdb_environ::get (const char *var) const
 {
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
+  size_t len = strlen (var);
 
-  for (; (s = *vector) != NULL; vector++)
-    if (strncmp (s, var, len) == 0 && s[len] == '=')
-      return &s[len + 1];
+  for (char *el : m_environ_vector)
+    if (el != NULL && match_var_in_string (el, var, len))
+      return &el[len + 1];
 
-  return 0;
+  return NULL;
 }
 
-/* Store the value in E of VAR as VALUE.  */
+/* See common/environ.h.  */
 
 void
-set_in_environ (struct gdb_environ *e, const char *var, const char *value)
+gdb_environ::set (const char *var, const char *value)
 {
-  int i;
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
-
-  for (i = 0; (s = vector[i]) != NULL; i++)
-    if (strncmp (s, var, len) == 0 && s[len] == '=')
-      break;
+  /* We have to unset the variable in the vector if it exists.  */
+  unset (var);
 
-  if (s == 0)
-    {
-      if (i == e->allocated)
-	{
-	  e->allocated += 10;
-	  vector = (char **) xrealloc ((char *) vector,
-				       (e->allocated + 1) * sizeof (char *));
-	  e->vector = vector;
-	}
-      vector[i + 1] = 0;
-    }
-  else
-    xfree (s);
-
-  s = (char *) xmalloc (len + strlen (value) + 2);
-  strcpy (s, var);
-  strcat (s, "=");
-  strcat (s, value);
-  vector[i] = s;
-
-  /* This used to handle setting the PATH and GNUTARGET variables
-     specially.  The latter has been replaced by "set gnutarget"
-     (which has worked since GDB 4.11).  The former affects searching
-     the PATH to find SHELL, and searching the PATH to find the
-     argument of "symbol-file" or "exec-file".  Maybe we should have
-     some kind of "set exec-path" for that.  But in any event, having
-     "set env" affect anything besides the inferior is a bad idea.
-     What if we want to change the environment we pass to the program
-     without afecting GDB's behavior?  */
-
-  return;
+  /* Insert the element before the last one, which is always NULL.  */
+  m_environ_vector.insert (m_environ_vector.end () - 1,
+			   concat (var, "=", value, NULL));
 }
 
-/* Remove the setting for variable VAR from environment E.  */
+/* See common/environ.h.  */
 
 void
-unset_in_environ (struct gdb_environ *e, const char *var)
+gdb_environ::unset (const char *var)
 {
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
+  size_t len = strlen (var);
+
+  /* We iterate until '.cend () - 1' because the last element is
+     always NULL.  */
+  for (std::vector<char *>::const_iterator el = m_environ_vector.cbegin ();
+       el != m_environ_vector.cend () - 1;
+       ++el)
+    if (match_var_in_string (*el, var, len))
+      {
+	xfree (*el);
+	m_environ_vector.erase (el);
+	break;
+      }
+}
 
-  for (; (s = *vector) != NULL; vector++)
-    {
-      if (strncmp (s, var, len) == 0 && s[len] == '=')
-	{
-	  xfree (s);
-	  /* Walk through the vector, shuffling args down by one, including
-	     the NULL terminator.  Can't use memcpy() here since the regions
-	     overlap, and memmove() might not be available.  */
-	  while ((vector[0] = vector[1]) != NULL)
-	    {
-	      vector++;
-	    }
-	  break;
-	}
-    }
+/* See common/environ.h.  */
+
+char **
+gdb_environ::envp () const
+{
+  return const_cast<char **> (&m_environ_vector[0]);
 }
diff --git a/gdb/common/environ.h b/gdb/common/environ.h
index 3ace69e..48c9d3a 100644
--- a/gdb/common/environ.h
+++ b/gdb/common/environ.h
@@ -17,33 +17,61 @@
 #if !defined (ENVIRON_H)
 #define ENVIRON_H 1
 
-/* We manipulate environments represented as these structures.  */
+#include <vector>
 
-struct gdb_environ
+/* Class that represents the environment variables as seen by the
+   inferior.  */
+
+class gdb_environ
+{
+public:
+  /* Regular constructor and destructor.  */
+  gdb_environ ()
+  {
+    /* Make sure that the vector contains at least a NULL element.
+       If/when we add more variables to it, NULL will always be the
+       last element.  */
+    m_environ_vector.push_back (NULL);
+  }
+
+  ~gdb_environ ()
   {
-    /* Number of usable slots allocated in VECTOR.
-       VECTOR always has one slot not counted here,
-       to hold the terminating zero.  */
-    int allocated;
-    /* A vector of slots, ALLOCATED + 1 of them.
-       The first few slots contain strings "VAR=VALUE"
-       and the next one contains zero.
-       Then come some unused slots.  */
-    char **vector;
-  };
+    clear ();
+  }
+
+  /* Move constructor.  */
+  gdb_environ (gdb_environ &&e)
+    : m_environ_vector (std::move (e.m_environ_vector))
+  {
+    e.m_environ_vector.clear ();
+    e.m_environ_vector.push_back (NULL);
+  }
+
+  /* Move assignment.  */
+  gdb_environ &operator= (gdb_environ &&e);
 
-extern struct gdb_environ *make_environ (void);
+  static gdb_environ from_host_environ ();
 
-extern void free_environ (struct gdb_environ *);
+  /* Clear the environment variables stored in the object.  */
+  void clear ();
 
-extern void init_environ (struct gdb_environ *);
+  /* Return the value in the environment for the variable VAR.  The
+     return pointer is only valid as long as VAR is not
+     removed/replaced from the environment.  */
+  const char *get (const char *var) const;
 
-extern char *get_in_environ (const struct gdb_environ *, const char *);
+  /* Store VAR=VALUE in the environment.  */
+  void set (const char *var, const char *value);
 
-extern void set_in_environ (struct gdb_environ *, const char *, const char *);
+  /* Unset VAR in environment.  */
+  void unset (const char *var);
 
-extern void unset_in_environ (struct gdb_environ *, const char *);
+  /* Return the environment vector represented as a 'char **'.  */
+  char **envp () const;
 
-extern char **environ_vector (struct gdb_environ *);
+private:
+  /* A vector containing the environment variables.  */
+  std::vector<char *> m_environ_vector;
+};
 
 #endif /* defined (ENVIRON_H) */
diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index c8e8d08..3d7cfe3 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -998,7 +998,7 @@ linux_create_inferior (const char *program,
 
   pid = fork_inferior (program,
 		       str_program_args.c_str (),
-		       environ_vector (get_environ ()), linux_ptrace_fun,
+		       get_environ ()->envp (), linux_ptrace_fun,
 		       NULL, NULL, NULL, NULL);
 
   do_cleanups (restore_personality);
diff --git a/gdb/gdbserver/lynx-low.c b/gdb/gdbserver/lynx-low.c
index 35160d6..77f570e 100644
--- a/gdb/gdbserver/lynx-low.c
+++ b/gdb/gdbserver/lynx-low.c
@@ -259,7 +259,7 @@ lynx_create_inferior (const char *program,
 
   pid = fork_inferior (program,
 		       str_program_args.c_str (),
-		       environ_vector (get_environ ()), lynx_ptrace_fun,
+		       get_environ ()->envp (), lynx_ptrace_fun,
 		       NULL, NULL, NULL, NULL);
 
   post_fork_inferior (pid, program);
diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c
index 1d7a8b0..3838351 100644
--- a/gdb/gdbserver/server.c
+++ b/gdb/gdbserver/server.c
@@ -42,7 +42,7 @@
 
 /* The environment to pass to the inferior when creating it.  */
 
-struct gdb_environ *our_environ = NULL;
+static gdb_environ our_environ;
 
 /* Start the inferior using a shell.  */
 
@@ -257,10 +257,10 @@ get_exec_file (int err)
 
 /* See server.h.  */
 
-struct gdb_environ *
+gdb_environ *
 get_environ ()
 {
-  return our_environ;
+  return &our_environ;
 }
 
 static int
@@ -3698,8 +3698,7 @@ captured_main (int argc, char *argv[])
     }
 
   /* Gather information about the environment.  */
-  our_environ = make_environ ();
-  init_environ (our_environ);
+  our_environ = gdb_environ::from_host_environ ();
 
   initialize_async_io ();
   initialize_low ();
diff --git a/gdb/gdbserver/server.h b/gdb/gdbserver/server.h
index 4de4244..46b614c 100644
--- a/gdb/gdbserver/server.h
+++ b/gdb/gdbserver/server.h
@@ -62,6 +62,7 @@ int vsnprintf(char *str, size_t size, const char *format, va_list ap);
 #include "mem-break.h"
 #include "gdbthread.h"
 #include "inferiors.h"
+#include "environ.h"
 
 /* Target-specific functions */
 
@@ -154,9 +155,8 @@ extern int in_queued_stop_replies (ptid_t ptid);
    inferior and PROGRAM is its name.  */
 extern void post_fork_inferior (int pid, const char *program);
 
-/* Get the 'struct gdb_environ *' being used in the current
-   session.  */
-extern struct gdb_environ *get_environ ();
+/* Get the gdb_environ being used in the current session.  */
+extern gdb_environ *get_environ ();
 
 extern target_waitstatus last_status;
 extern ptid_t last_ptid;
diff --git a/gdb/gdbserver/spu-low.c b/gdb/gdbserver/spu-low.c
index 0f770a0..6362502 100644
--- a/gdb/gdbserver/spu-low.c
+++ b/gdb/gdbserver/spu-low.c
@@ -289,7 +289,7 @@ spu_create_inferior (const char *program,
 
   pid = fork_inferior (program,
 		       str_program_args.c_str (),
-		       environ_vector (get_environ ()), spu_ptrace_fun,
+		       get_environ ()->envp (), spu_ptrace_fun,
 		       NULL, NULL, NULL, NULL);
 
   post_fork_inferior (pid, program);
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index d551639..defa7b0 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -610,7 +610,7 @@ run_command_1 (char *args, int from_tty, int tbreak_at_main)
      the value now.  */
   run_target->to_create_inferior (run_target, exec_file,
 				  std::string (get_inferior_args ()),
-				  environ_vector (current_inferior ()->environment),
+				  current_inferior ()->environment.envp (),
 				  from_tty);
   /* to_create_inferior should push the target, so after this point we
      shouldn't refer to run_target again.  */
@@ -2131,7 +2131,7 @@ environment_info (char *var, int from_tty)
 {
   if (var)
     {
-      char *val = get_in_environ (current_inferior ()->environment, var);
+      const char *val = current_inferior ()->environment.get (var);
 
       if (val)
 	{
@@ -2149,11 +2149,11 @@ environment_info (char *var, int from_tty)
     }
   else
     {
-      char **vector = environ_vector (current_inferior ()->environment);
+      char **envp = current_inferior ()->environment.envp ();
 
-      while (*vector)
+      for (int idx = 0; envp[idx] != NULL; ++idx)
 	{
-	  puts_filtered (*vector++);
+	  puts_filtered (envp[idx]);
 	  puts_filtered ("\n");
 	}
     }
@@ -2215,10 +2215,10 @@ set_environment_command (char *arg, int from_tty)
       printf_filtered (_("Setting environment variable "
 			 "\"%s\" to null value.\n"),
 		       var);
-      set_in_environ (current_inferior ()->environment, var, "");
+      current_inferior ()->environment.set (var, "");
     }
   else
-    set_in_environ (current_inferior ()->environment, var, val);
+    current_inferior ()->environment.set (var, val);
   xfree (var);
 }
 
@@ -2230,13 +2230,10 @@ unset_environment_command (char *var, int from_tty)
       /* If there is no argument, delete all environment variables.
          Ask for confirmation if reading from the terminal.  */
       if (!from_tty || query (_("Delete all environment variables? ")))
-	{
-	  free_environ (current_inferior ()->environment);
-	  current_inferior ()->environment = make_environ ();
-	}
+	current_inferior ()->environment = gdb_environ::from_host_environ ();
     }
   else
-    unset_in_environ (current_inferior ()->environment, var);
+    current_inferior ()->environment.unset (var);
 }
 
 /* Handle the execution path (PATH variable).  */
@@ -2247,8 +2244,7 @@ static void
 path_info (char *args, int from_tty)
 {
   puts_filtered ("Executable and object file path: ");
-  puts_filtered (get_in_environ (current_inferior ()->environment,
-				 path_var_name));
+  puts_filtered (current_inferior ()->environment.get (path_var_name));
   puts_filtered ("\n");
 }
 
@@ -2261,13 +2257,13 @@ path_command (char *dirname, int from_tty)
   const char *env;
 
   dont_repeat ();
-  env = get_in_environ (current_inferior ()->environment, path_var_name);
+  env = current_inferior ()->environment.get (path_var_name);
   /* Can be null if path is not set.  */
   if (!env)
     env = "";
   exec_path = xstrdup (env);
   mod_path (dirname, &exec_path);
-  set_in_environ (current_inferior ()->environment, path_var_name, exec_path);
+  current_inferior ()->environment.set (path_var_name, exec_path);
   xfree (exec_path);
   if (from_tty)
     path_info ((char *) NULL, from_tty);
diff --git a/gdb/inferior.c b/gdb/inferior.c
index 0b655f4..9fa2dad 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -81,7 +81,6 @@ inferior::~inferior ()
   inferior_free_data (inf);
   xfree (inf->args);
   xfree (inf->terminal);
-  free_environ (inf->environment);
   target_desc_info_free (inf->tdesc_info);
   xfree (inf->priv);
 }
@@ -89,10 +88,9 @@ inferior::~inferior ()
 inferior::inferior (int pid_)
   : num (++highest_inferior_num),
     pid (pid_),
-    environment (make_environ ()),
+    environment (gdb_environ::from_host_environ ()),
     registry_data ()
 {
-  init_environ (this->environment);
   inferior_alloc_data (this);
 }
 
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 1c541b7..8ada4f8 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -30,7 +30,6 @@ struct regcache;
 struct ui_out;
 struct terminal_info;
 struct target_desc_info;
-struct gdb_environ;
 struct continuation;
 struct inferior;
 
@@ -43,6 +42,9 @@ struct inferior;
 /* For struct frame_id.  */
 #include "frame.h"
 
+/* For gdb_environ.  */
+#include "environ.h"
+
 #include "progspace.h"
 #include "registry.h"
 
@@ -363,7 +365,7 @@ public:
 
   /* Environment to use for running inferior,
      in format described in environ.h.  */
-  gdb_environ *environment = NULL;
+  gdb_environ environment;
 
   /* True if this child process was attached rather than forked.  */
   bool attach_flag = false;
diff --git a/gdb/mi/mi-cmd-env.c b/gdb/mi/mi-cmd-env.c
index 97be139..bf4578c 100644
--- a/gdb/mi/mi-cmd-env.c
+++ b/gdb/mi/mi-cmd-env.c
@@ -167,7 +167,7 @@ mi_cmd_env_path (const char *command, char **argv, int argc)
   else
     {
       /* Otherwise, get current path to modify.  */
-      env = get_in_environ (current_inferior ()->environment, path_var_name);
+      env = current_inferior ()->environment.get (path_var_name);
 
       /* Can be null if path is not set.  */
       if (!env)
@@ -178,9 +178,9 @@ mi_cmd_env_path (const char *command, char **argv, int argc)
   for (i = argc - 1; i >= 0; --i)
     env_mod_path (argv[i], &exec_path);
 
-  set_in_environ (current_inferior ()->environment, path_var_name, exec_path);
+  current_inferior ()->environment.set (path_var_name, exec_path);
   xfree (exec_path);
-  env = get_in_environ (current_inferior ()->environment, path_var_name);
+  env = current_inferior ()->environment.get (path_var_name);
   uiout->field_string ("path", env);
 }
 
diff --git a/gdb/solib.c b/gdb/solib.c
index 491c18a..788cf15 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -350,16 +350,15 @@ solib_find_1 (const char *in_pathname, int *fd, int is_solib)
 
   /* If not found, next search the inferior's $PATH environment variable.  */
   if (found_file < 0 && sysroot == NULL)
-    found_file = openp (get_in_environ (current_inferior ()->environment,
-					"PATH"),
+    found_file = openp (current_inferior ()->environment.get ("PATH"),
 			OPF_TRY_CWD_FIRST | OPF_RETURN_REALPATH, in_pathname,
 			O_RDONLY | O_BINARY, &temp_pathname);
 
   /* If not found, and we're looking for a solib, next search the
      inferior's $LD_LIBRARY_PATH environment variable.  */
   if (is_solib && found_file < 0 && sysroot == NULL)
-    found_file = openp (get_in_environ (current_inferior ()->environment,
-					"LD_LIBRARY_PATH"),
+    found_file = openp (current_inferior ()->environment.get
+			("LD_LIBRARY_PATH"),
 			OPF_TRY_CWD_FIRST | OPF_RETURN_REALPATH, in_pathname,
 			O_RDONLY | O_BINARY, &temp_pathname);
 
diff --git a/gdb/unittests/environ-selftests.c b/gdb/unittests/environ-selftests.c
new file mode 100644
index 0000000..8670d58
--- /dev/null
+++ b/gdb/unittests/environ-selftests.c
@@ -0,0 +1,129 @@
+/* Self tests for gdb_environ for GDB, the GNU debugger.
+
+   Copyright (C) 2017 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "selftest.h"
+#include "common/environ.h"
+
+namespace selftests {
+namespace gdb_environ_tests {
+
+static void
+run_tests ()
+{
+  /* Set a test environment variable.  This will be unset at the end
+     of this function.  */
+  if (setenv ("GDB_SELFTEST_ENVIRON", "1", 1) != 0)
+    error (_("Could not set environment variable for testing."));
+
+  gdb_environ env;
+
+  /* When the vector is initialized, there should always be one NULL
+     element in it.  */
+  SELF_CHECK (env.envp ()[0] == NULL);
+
+  /* Make sure that there is no other element.  */
+  SELF_CHECK (env.get ("PWD") == NULL);
+
+  /* Check if unset followed by a set in an empty vector works.  */
+  env.set ("PWD", "test");
+  SELF_CHECK (strcmp (env.get ("PWD"), "test") == 0);
+  /* The second element must be NULL.  */
+  SELF_CHECK (env.envp ()[1] == NULL);
+  env.unset ("PWD");
+  SELF_CHECK (env.envp ()[0] == NULL);
+
+  /* Initialize the environment vector using the host's environ.  */
+  env = gdb_environ::from_host_environ ();
+
+  /* Our test environment variable should be present at the
+     vector.  */
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0);
+
+  /* Set our test variable to another value.  */
+  env.set ("GDB_SELFTEST_ENVIRON", "test");
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "test") == 0);
+
+  /* And unset our test variable.  The variable still exists in the
+     host's environment, but doesn't exist in our vector.  */
+  env.unset ("GDB_SELFTEST_ENVIRON");
+  SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL);
+
+  /* Re-set the test variable.  */
+  env.set ("GDB_SELFTEST_ENVIRON", "1");
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0);
+
+  /* When we clear our environ vector, there should be only one
+     element on it (NULL), and we shouldn't be able to get our test
+     variable.  */
+  env.clear ();
+  SELF_CHECK (env.envp ()[0] == NULL);
+  SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL);
+
+  /* Reinitialize our environ vector using the host environ.  We
+     should be able to see one (and only one) instance of the test
+     variable.  */
+  env = gdb_environ::from_host_environ ();
+  char **envp = env.envp ();
+  int num_found = 0;
+
+  for (size_t i = 0; envp[i] != NULL; ++i)
+    if (strcmp (envp[i], "GDB_SELFTEST_ENVIRON=1") == 0)
+      ++num_found;
+  SELF_CHECK (num_found == 1);
+
+  /* Get rid of our test variable.  */
+  unsetenv ("GDB_SELFTEST_ENVIRON");
+
+  /* Test the case when we set a variable A, then set a variable B,
+     then unset A, and make sure that we cannot find A in the environ
+     vector, but can still find B.  */
+  env.set ("GDB_SELFTEST_ENVIRON_1", "aaa");
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON_1"), "aaa") == 0);
+
+  env.set ("GDB_SELFTEST_ENVIRON_2", "bbb");
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON_2"), "bbb") == 0);
+
+  env.unset ("GDB_SELFTEST_ENVIRON_1");
+  SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON_1") == NULL);
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON_2"), "bbb") == 0);
+
+  env.clear ();
+
+  /* Test that after a std::move the moved-from object is left at a
+     valid state (i.e., its only element is NULL).  */
+  env.set ("A", "1");
+  SELF_CHECK (strcmp (env.get ("A"), "1") == 0);
+  gdb_environ env2;
+  env2 = std::move (env);
+  SELF_CHECK (env.envp ()[0] == NULL);
+  SELF_CHECK (strcmp (env2.get ("A"), "1") == 0);
+  SELF_CHECK (env2.envp ()[1] == NULL);
+  env.set ("B", "2");
+  SELF_CHECK (strcmp (env.get ("B"), "2") == 0);
+  SELF_CHECK (env.envp ()[1] == NULL);
+}
+} /* namespace gdb_environ */
+} /* namespace selftests */
+
+void
+_initialize_environ_selftests ()
+{
+  register_self_test (selftests::gdb_environ_tests::run_tests);
+}
-- 
2.9.3

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v6] C++ify gdb/common/environ.c
  2017-06-19 18:23                     ` Sergio Durigan Junior
@ 2017-06-19 18:36                       ` Pedro Alves
  2017-06-19 18:38                         ` Pedro Alves
  0 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2017-06-19 18:36 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: Simon Marchi, GDB Patches


On 06/19/2017 07:23 PM, Sergio Durigan Junior wrote:
> On Monday, June 19 2017, Pedro Alves wrote:

>> Missed fixing move ctor?
>>
>> +  /* Move constructor.  */
>> +  gdb_environ (gdb_environ &&e)
>> +    : m_environ_vector (std::move (e.m_environ_vector))
>> +  {}
> 
> Indeed.  Fixed now.  I'll submit v7.

Make sure to add unit tests for the move ctor.  E.g., reuse
the move assign tests.

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v6] C++ify gdb/common/environ.c
  2017-06-19 18:36                       ` Pedro Alves
@ 2017-06-19 18:38                         ` Pedro Alves
  0 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2017-06-19 18:38 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: Simon Marchi, GDB Patches

On 06/19/2017 07:36 PM, Pedro Alves wrote:
> 
> On 06/19/2017 07:23 PM, Sergio Durigan Junior wrote:
>> On Monday, June 19 2017, Pedro Alves wrote:
> 
>>> Missed fixing move ctor?
>>>
>>> +  /* Move constructor.  */
>>> +  gdb_environ (gdb_environ &&e)
>>> +    : m_environ_vector (std::move (e.m_environ_vector))
>>> +  {}
>>
>> Indeed.  Fixed now.  I'll submit v7.
> 
> Make sure to add unit tests for the move ctor.  E.g., reuse
> the move assign tests.

And self-move tests...

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 47+ messages in thread

* [PATCH v8] C++ify gdb/common/environ.c
  2017-04-13  4:05 [PATCH] C++ify gdb/common/environ.c Sergio Durigan Junior
                   ` (5 preceding siblings ...)
  2017-06-19 18:27 ` [PATCH v7] " Sergio Durigan Junior
@ 2017-06-20  3:27 ` Sergio Durigan Junior
  2017-06-20 12:13   ` Pedro Alves
  2017-06-20 12:46   ` Simon Marchi
  6 siblings, 2 replies; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-06-20  3:27 UTC (permalink / raw)
  To: GDB Patches; +Cc: Simon Marchi, Pedro Alves, Sergio Durigan Junior

Changes from v7:

- Add more unittests to check for self-move and move constructors.

As part of the preparation necessary for my upcoming task, I'd like to
propose that we turn gdb_environ into a class.  The approach taken
here is simple: the class gdb_environ contains everything that is
needed to manipulate the environment variables.  These variables are
stored in an std::vector<char *>, which can be converted to a 'char
**' and passed as argument to functions that need it.

The usage has not changed much.  As per Pedro's suggestion, this class
uses a static factory method initialization.  This means that when an
instance is created, it is initially empty.  When needed, it has to be
initialized using the static method 'from_host_environ'.

As mentioned before, this is a preparation for an upcoming work that I
will be posting in the next few weeks or so.  For that work, I'll
probably create another data structure that will contain all the
environment variables that were set by the user using the 'set
environment' command, because I'll need access to them.  This will be
much easier with the class-ification of gdb_environ.

As noted, this has been regression-tested with the new version of
environ.exp and no regressions were found.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	* Makefile.in (SUBDIR_UNITTESTS_SRCS): Add
	'unittests/environ-selftests.c'.
	(SUBDIR_UNITTESTS_OBS): Add 'environ-selftests.o'.
	* charset.c (find_charset_names): Declare object 'iconv_env'.
	Update code to use 'iconv_env' object.  Remove call to
	'free_environ'.
	* common/environ.c: Include <utility>.
	(make_environ): Delete function.
	(free_environ): Delete function.
	(gdb_environ::clear): New function.
	(gdb_environ::operator=): New function.
	(gdb_environ::get): Likewise.
	(environ_vector): Delete function.
	(set_in_environ): Delete function.
	(gdb_environ::set): New function.
	(unset_in_environ): Delete function.
	(gdb_environ::unset): New function.
	(gdb_environ::envp): Likewise.
	* common/environ.h: Include <vector>.
	(struct gdb_environ): Delete; transform into...
	(class gdb_environ): ... this class.
	(free_environ): Delete prototype.
	(init_environ, get_in_environ, set_in_environ, unset_in_environ,
	environ_vector): Likewise.
	* infcmd.c (run_command_1): Update code to call
	'envp' from 'gdb_environ' class.
	(environment_info): Update code to call methods from 'gdb_environ'
	class.
	(unset_environment_command): Likewise.
	(path_info): Likewise.
	(path_command): Likewise.
	* inferior.c (inferior::~inferior): Delete call to 'free_environ'.
	(inferior::inferior): Initialize 'environment' using the host's
	information.
	* inferior.h: Remove forward declaration of 'struct gdb_environ'.
	Include "environ.h".
	(class inferior) <environment>: Change type from 'struct
	gdb_environ' to 'gdb_environ'.
	* mi/mi-cmd-env.c (mi_cmd_env_path): Update code to call
	methods from 'gdb_environ' class.
	* solib.c (solib_find_1): Likewise
	* unittests/environ-selftests.c: New file.

gdb/gdbserver/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	* linux-low.c (linux_create_inferior): Adjust code to access the
	environment information via 'gdb_environ' class.
	* lynx-low.c (lynx_create_inferior): Likewise.
	* server.c (our_environ): Make it an instance of 'gdb_environ'.
	(get_environ): Return a pointer to 'our_environ'.
	(captured_main): Initialize 'our_environ'.
	* server.h (get_environ): Adjust prototype.
	* spu-low.c (spu_create_inferior): Adjust code to access the
	environment information via 'gdb_environ' class.
---
 gdb/Makefile.in                   |   2 +
 gdb/charset.c                     |  11 +--
 gdb/common/environ.c              | 203 +++++++++++++++-----------------------
 gdb/common/environ.h              |  68 +++++++++----
 gdb/gdbserver/linux-low.c         |   2 +-
 gdb/gdbserver/lynx-low.c          |   2 +-
 gdb/gdbserver/server.c            |   9 +-
 gdb/gdbserver/server.h            |   6 +-
 gdb/gdbserver/spu-low.c           |   2 +-
 gdb/infcmd.c                      |  28 +++---
 gdb/inferior.c                    |   4 +-
 gdb/inferior.h                    |   6 +-
 gdb/mi/mi-cmd-env.c               |   6 +-
 gdb/solib.c                       |   7 +-
 gdb/unittests/environ-selftests.c | 151 ++++++++++++++++++++++++++++
 15 files changed, 318 insertions(+), 189 deletions(-)
 create mode 100644 gdb/unittests/environ-selftests.c

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 153a5dd..b27f698 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -526,6 +526,7 @@ SUBDIR_PYTHON_LDFLAGS =
 SUBDIR_PYTHON_CFLAGS =
 
 SUBDIR_UNITTESTS_SRCS = \
+	unittests/environ-selftests.c \
 	unittests/function-view-selftests.c \
 	unittests/offset-type-selftests.c \
 	unittests/optional-selftests.c \
@@ -533,6 +534,7 @@ SUBDIR_UNITTESTS_SRCS = \
 	unittests/scoped_restore-selftests.c
 
 SUBDIR_UNITTESTS_OBS = \
+	environ-selftests.o \
 	function-view-selftests.o \
 	offset-type-selftests.o \
 	optional-selftests.o \
diff --git a/gdb/charset.c b/gdb/charset.c
index dbe46a4..be95bbe 100644
--- a/gdb/charset.c
+++ b/gdb/charset.c
@@ -794,16 +794,14 @@ find_charset_names (void)
   int err, status;
   int fail = 1;
   int flags;
-  struct gdb_environ *iconv_env;
+  gdb_environ iconv_env = gdb_environ::from_host_environ ();
   char *iconv_program;
 
   /* Older iconvs, e.g. 2.2.2, don't omit the intro text if stdout is
      not a tty.  We need to recognize it and ignore it.  This text is
      subject to translation, so force LANGUAGE=C.  */
-  iconv_env = make_environ ();
-  init_environ (iconv_env);
-  set_in_environ (iconv_env, "LANGUAGE", "C");
-  set_in_environ (iconv_env, "LC_ALL", "C");
+  iconv_env.set ("LANGUAGE", "C");
+  iconv_env.set ("LC_ALL", "C");
 
   child = pex_init (PEX_USE_PIPES, "iconv", NULL);
 
@@ -827,7 +825,7 @@ find_charset_names (void)
   /* Note that we simply ignore errors here.  */
   if (!pex_run_in_environment (child, flags,
 			       args[0], const_cast<char **> (args),
-			       environ_vector (iconv_env),
+			       iconv_env.envp (),
 			       NULL, NULL, &err))
     {
       FILE *in = pex_read_output (child, 0);
@@ -901,7 +899,6 @@ find_charset_names (void)
 
   xfree (iconv_program);
   pex_free (child);
-  free_environ (iconv_env);
 
   if (fail)
     {
diff --git a/gdb/common/environ.c b/gdb/common/environ.c
index 3145d01..c81b9a4 100644
--- a/gdb/common/environ.c
+++ b/gdb/common/environ.c
@@ -18,165 +18,120 @@
 #include "common-defs.h"
 #include "environ.h"
 #include <algorithm>
-\f
+#include <utility>
 
-/* Return a new environment object.  */
+/* See common/environ.h.  */
 
-struct gdb_environ *
-make_environ (void)
+gdb_environ &
+gdb_environ::operator= (gdb_environ &&e)
 {
-  struct gdb_environ *e;
-
-  e = XNEW (struct gdb_environ);
-
-  e->allocated = 10;
-  e->vector = (char **) xmalloc ((e->allocated + 1) * sizeof (char *));
-  e->vector[0] = 0;
-  return e;
+  /* Are we self-moving?  */
+  if (&e == this)
+    return *this;
+
+  m_environ_vector = std::move (e.m_environ_vector);
+  e.m_environ_vector.clear ();
+  e.m_environ_vector.push_back (NULL);
+  return *this;
 }
 
-/* Free an environment and all the strings in it.  */
+/* Create a gdb_environ object using the host's environment
+   variables.  */
 
-void
-free_environ (struct gdb_environ *e)
-{
-  char **vector = e->vector;
-
-  while (*vector)
-    xfree (*vector++);
-
-  xfree (e->vector);
-  xfree (e);
-}
-
-/* Copy the environment given to this process into E.
-   Also copies all the strings in it, so we can be sure
-   that all strings in these environments are safe to free.  */
-
-void
-init_environ (struct gdb_environ *e)
+gdb_environ gdb_environ::from_host_environ ()
 {
   extern char **environ;
-  int i;
+  gdb_environ e;
 
   if (environ == NULL)
-    return;
-
-  for (i = 0; environ[i]; i++) /*EMPTY */ ;
+    return e;
 
-  if (e->allocated < i)
+  for (int i = 0; environ[i] != NULL; ++i)
     {
-      e->allocated = std::max (i, e->allocated + 10);
-      e->vector = (char **) xrealloc ((char *) e->vector,
-				      (e->allocated + 1) * sizeof (char *));
+      /* Make sure we add the element before the last (NULL).  */
+      e.m_environ_vector.insert (e.m_environ_vector.end () - 1,
+				 xstrdup (environ[i]));
     }
 
-  memcpy (e->vector, environ, (i + 1) * sizeof (char *));
+  return e;
+}
 
-  while (--i >= 0)
-    {
-      int len = strlen (e->vector[i]);
-      char *newobj = (char *) xmalloc (len + 1);
+/* See common/environ.h.  */
 
-      memcpy (newobj, e->vector[i], len + 1);
-      e->vector[i] = newobj;
-    }
+void
+gdb_environ::clear ()
+{
+  for (char *v : m_environ_vector)
+    xfree (v);
+  m_environ_vector.clear ();
+  /* Always add the NULL element.  */
+  m_environ_vector.push_back (NULL);
 }
 
-/* Return the vector of environment E.
-   This is used to get something to pass to execve.  */
+/* Helper function to check if STRING contains an environment variable
+   assignment of VAR, i.e., if STRING starts with 'VAR='.  Return true
+   if it contains, false otherwise.  */
 
-char **
-environ_vector (struct gdb_environ *e)
+static bool
+match_var_in_string (char *string, const char *var, size_t var_len)
 {
-  return e->vector;
+  if (strncmp (string, var, var_len) == 0 && string[var_len] == '=')
+    return true;
+
+  return false;
 }
-\f
-/* Return the value in environment E of variable VAR.  */
 
-char *
-get_in_environ (const struct gdb_environ *e, const char *var)
+/* See common/environ.h.  */
+
+const char *
+gdb_environ::get (const char *var) const
 {
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
+  size_t len = strlen (var);
 
-  for (; (s = *vector) != NULL; vector++)
-    if (strncmp (s, var, len) == 0 && s[len] == '=')
-      return &s[len + 1];
+  for (char *el : m_environ_vector)
+    if (el != NULL && match_var_in_string (el, var, len))
+      return &el[len + 1];
 
-  return 0;
+  return NULL;
 }
 
-/* Store the value in E of VAR as VALUE.  */
+/* See common/environ.h.  */
 
 void
-set_in_environ (struct gdb_environ *e, const char *var, const char *value)
+gdb_environ::set (const char *var, const char *value)
 {
-  int i;
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
+  /* We have to unset the variable in the vector if it exists.  */
+  unset (var);
 
-  for (i = 0; (s = vector[i]) != NULL; i++)
-    if (strncmp (s, var, len) == 0 && s[len] == '=')
-      break;
-
-  if (s == 0)
-    {
-      if (i == e->allocated)
-	{
-	  e->allocated += 10;
-	  vector = (char **) xrealloc ((char *) vector,
-				       (e->allocated + 1) * sizeof (char *));
-	  e->vector = vector;
-	}
-      vector[i + 1] = 0;
-    }
-  else
-    xfree (s);
-
-  s = (char *) xmalloc (len + strlen (value) + 2);
-  strcpy (s, var);
-  strcat (s, "=");
-  strcat (s, value);
-  vector[i] = s;
-
-  /* This used to handle setting the PATH and GNUTARGET variables
-     specially.  The latter has been replaced by "set gnutarget"
-     (which has worked since GDB 4.11).  The former affects searching
-     the PATH to find SHELL, and searching the PATH to find the
-     argument of "symbol-file" or "exec-file".  Maybe we should have
-     some kind of "set exec-path" for that.  But in any event, having
-     "set env" affect anything besides the inferior is a bad idea.
-     What if we want to change the environment we pass to the program
-     without afecting GDB's behavior?  */
-
-  return;
+  /* Insert the element before the last one, which is always NULL.  */
+  m_environ_vector.insert (m_environ_vector.end () - 1,
+			   concat (var, "=", value, NULL));
 }
 
-/* Remove the setting for variable VAR from environment E.  */
+/* See common/environ.h.  */
 
 void
-unset_in_environ (struct gdb_environ *e, const char *var)
+gdb_environ::unset (const char *var)
 {
-  int len = strlen (var);
-  char **vector = e->vector;
-  char *s;
+  size_t len = strlen (var);
+
+  /* We iterate until '.cend () - 1' because the last element is
+     always NULL.  */
+  for (std::vector<char *>::const_iterator el = m_environ_vector.cbegin ();
+       el != m_environ_vector.cend () - 1;
+       ++el)
+    if (match_var_in_string (*el, var, len))
+      {
+	xfree (*el);
+	m_environ_vector.erase (el);
+	break;
+      }
+}
 
-  for (; (s = *vector) != NULL; vector++)
-    {
-      if (strncmp (s, var, len) == 0 && s[len] == '=')
-	{
-	  xfree (s);
-	  /* Walk through the vector, shuffling args down by one, including
-	     the NULL terminator.  Can't use memcpy() here since the regions
-	     overlap, and memmove() might not be available.  */
-	  while ((vector[0] = vector[1]) != NULL)
-	    {
-	      vector++;
-	    }
-	  break;
-	}
-    }
+/* See common/environ.h.  */
+
+char **
+gdb_environ::envp () const
+{
+  return const_cast<char **> (&m_environ_vector[0]);
 }
diff --git a/gdb/common/environ.h b/gdb/common/environ.h
index 3ace69e..83b5e00 100644
--- a/gdb/common/environ.h
+++ b/gdb/common/environ.h
@@ -17,33 +17,63 @@
 #if !defined (ENVIRON_H)
 #define ENVIRON_H 1
 
-/* We manipulate environments represented as these structures.  */
+#include <vector>
 
-struct gdb_environ
+/* Class that represents the environment variables as seen by the
+   inferior.  */
+
+class gdb_environ
+{
+public:
+  /* Regular constructor and destructor.  */
+  gdb_environ ()
+  {
+    /* Make sure that the vector contains at least a NULL element.
+       If/when we add more variables to it, NULL will always be the
+       last element.  */
+    m_environ_vector.push_back (NULL);
+  }
+
+  ~gdb_environ ()
   {
-    /* Number of usable slots allocated in VECTOR.
-       VECTOR always has one slot not counted here,
-       to hold the terminating zero.  */
-    int allocated;
-    /* A vector of slots, ALLOCATED + 1 of them.
-       The first few slots contain strings "VAR=VALUE"
-       and the next one contains zero.
-       Then come some unused slots.  */
-    char **vector;
-  };
+    clear ();
+  }
+
+  /* Move constructor.  */
+  gdb_environ (gdb_environ &&e)
+    : m_environ_vector (std::move (e.m_environ_vector))
+  {
+    /* Make sure that the moved-from vector is left at a valid
+       state (only one NULL element).  */
+    e.m_environ_vector.clear ();
+    e.m_environ_vector.push_back (NULL);
+  }
+
+  /* Move assignment.  */
+  gdb_environ &operator= (gdb_environ &&e);
 
-extern struct gdb_environ *make_environ (void);
+  static gdb_environ from_host_environ ();
 
-extern void free_environ (struct gdb_environ *);
+  /* Clear the environment variables stored in the object.  */
+  void clear ();
 
-extern void init_environ (struct gdb_environ *);
+  /* Return the value in the environment for the variable VAR.  The
+     return pointer is only valid as long as VAR is not
+     removed/replaced from the environment.  */
+  const char *get (const char *var) const;
 
-extern char *get_in_environ (const struct gdb_environ *, const char *);
+  /* Store VAR=VALUE in the environment.  */
+  void set (const char *var, const char *value);
 
-extern void set_in_environ (struct gdb_environ *, const char *, const char *);
+  /* Unset VAR in environment.  */
+  void unset (const char *var);
 
-extern void unset_in_environ (struct gdb_environ *, const char *);
+  /* Return the environment vector represented as a 'char **'.  */
+  char **envp () const;
 
-extern char **environ_vector (struct gdb_environ *);
+private:
+  /* A vector containing the environment variables.  */
+  std::vector<char *> m_environ_vector;
+};
 
 #endif /* defined (ENVIRON_H) */
diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index c8e8d08..3d7cfe3 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -998,7 +998,7 @@ linux_create_inferior (const char *program,
 
   pid = fork_inferior (program,
 		       str_program_args.c_str (),
-		       environ_vector (get_environ ()), linux_ptrace_fun,
+		       get_environ ()->envp (), linux_ptrace_fun,
 		       NULL, NULL, NULL, NULL);
 
   do_cleanups (restore_personality);
diff --git a/gdb/gdbserver/lynx-low.c b/gdb/gdbserver/lynx-low.c
index 35160d6..77f570e 100644
--- a/gdb/gdbserver/lynx-low.c
+++ b/gdb/gdbserver/lynx-low.c
@@ -259,7 +259,7 @@ lynx_create_inferior (const char *program,
 
   pid = fork_inferior (program,
 		       str_program_args.c_str (),
-		       environ_vector (get_environ ()), lynx_ptrace_fun,
+		       get_environ ()->envp (), lynx_ptrace_fun,
 		       NULL, NULL, NULL, NULL);
 
   post_fork_inferior (pid, program);
diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c
index 1d7a8b0..3838351 100644
--- a/gdb/gdbserver/server.c
+++ b/gdb/gdbserver/server.c
@@ -42,7 +42,7 @@
 
 /* The environment to pass to the inferior when creating it.  */
 
-struct gdb_environ *our_environ = NULL;
+static gdb_environ our_environ;
 
 /* Start the inferior using a shell.  */
 
@@ -257,10 +257,10 @@ get_exec_file (int err)
 
 /* See server.h.  */
 
-struct gdb_environ *
+gdb_environ *
 get_environ ()
 {
-  return our_environ;
+  return &our_environ;
 }
 
 static int
@@ -3698,8 +3698,7 @@ captured_main (int argc, char *argv[])
     }
 
   /* Gather information about the environment.  */
-  our_environ = make_environ ();
-  init_environ (our_environ);
+  our_environ = gdb_environ::from_host_environ ();
 
   initialize_async_io ();
   initialize_low ();
diff --git a/gdb/gdbserver/server.h b/gdb/gdbserver/server.h
index 4de4244..46b614c 100644
--- a/gdb/gdbserver/server.h
+++ b/gdb/gdbserver/server.h
@@ -62,6 +62,7 @@ int vsnprintf(char *str, size_t size, const char *format, va_list ap);
 #include "mem-break.h"
 #include "gdbthread.h"
 #include "inferiors.h"
+#include "environ.h"
 
 /* Target-specific functions */
 
@@ -154,9 +155,8 @@ extern int in_queued_stop_replies (ptid_t ptid);
    inferior and PROGRAM is its name.  */
 extern void post_fork_inferior (int pid, const char *program);
 
-/* Get the 'struct gdb_environ *' being used in the current
-   session.  */
-extern struct gdb_environ *get_environ ();
+/* Get the gdb_environ being used in the current session.  */
+extern gdb_environ *get_environ ();
 
 extern target_waitstatus last_status;
 extern ptid_t last_ptid;
diff --git a/gdb/gdbserver/spu-low.c b/gdb/gdbserver/spu-low.c
index 0f770a0..6362502 100644
--- a/gdb/gdbserver/spu-low.c
+++ b/gdb/gdbserver/spu-low.c
@@ -289,7 +289,7 @@ spu_create_inferior (const char *program,
 
   pid = fork_inferior (program,
 		       str_program_args.c_str (),
-		       environ_vector (get_environ ()), spu_ptrace_fun,
+		       get_environ ()->envp (), spu_ptrace_fun,
 		       NULL, NULL, NULL, NULL);
 
   post_fork_inferior (pid, program);
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index d551639..defa7b0 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -610,7 +610,7 @@ run_command_1 (char *args, int from_tty, int tbreak_at_main)
      the value now.  */
   run_target->to_create_inferior (run_target, exec_file,
 				  std::string (get_inferior_args ()),
-				  environ_vector (current_inferior ()->environment),
+				  current_inferior ()->environment.envp (),
 				  from_tty);
   /* to_create_inferior should push the target, so after this point we
      shouldn't refer to run_target again.  */
@@ -2131,7 +2131,7 @@ environment_info (char *var, int from_tty)
 {
   if (var)
     {
-      char *val = get_in_environ (current_inferior ()->environment, var);
+      const char *val = current_inferior ()->environment.get (var);
 
       if (val)
 	{
@@ -2149,11 +2149,11 @@ environment_info (char *var, int from_tty)
     }
   else
     {
-      char **vector = environ_vector (current_inferior ()->environment);
+      char **envp = current_inferior ()->environment.envp ();
 
-      while (*vector)
+      for (int idx = 0; envp[idx] != NULL; ++idx)
 	{
-	  puts_filtered (*vector++);
+	  puts_filtered (envp[idx]);
 	  puts_filtered ("\n");
 	}
     }
@@ -2215,10 +2215,10 @@ set_environment_command (char *arg, int from_tty)
       printf_filtered (_("Setting environment variable "
 			 "\"%s\" to null value.\n"),
 		       var);
-      set_in_environ (current_inferior ()->environment, var, "");
+      current_inferior ()->environment.set (var, "");
     }
   else
-    set_in_environ (current_inferior ()->environment, var, val);
+    current_inferior ()->environment.set (var, val);
   xfree (var);
 }
 
@@ -2230,13 +2230,10 @@ unset_environment_command (char *var, int from_tty)
       /* If there is no argument, delete all environment variables.
          Ask for confirmation if reading from the terminal.  */
       if (!from_tty || query (_("Delete all environment variables? ")))
-	{
-	  free_environ (current_inferior ()->environment);
-	  current_inferior ()->environment = make_environ ();
-	}
+	current_inferior ()->environment = gdb_environ::from_host_environ ();
     }
   else
-    unset_in_environ (current_inferior ()->environment, var);
+    current_inferior ()->environment.unset (var);
 }
 
 /* Handle the execution path (PATH variable).  */
@@ -2247,8 +2244,7 @@ static void
 path_info (char *args, int from_tty)
 {
   puts_filtered ("Executable and object file path: ");
-  puts_filtered (get_in_environ (current_inferior ()->environment,
-				 path_var_name));
+  puts_filtered (current_inferior ()->environment.get (path_var_name));
   puts_filtered ("\n");
 }
 
@@ -2261,13 +2257,13 @@ path_command (char *dirname, int from_tty)
   const char *env;
 
   dont_repeat ();
-  env = get_in_environ (current_inferior ()->environment, path_var_name);
+  env = current_inferior ()->environment.get (path_var_name);
   /* Can be null if path is not set.  */
   if (!env)
     env = "";
   exec_path = xstrdup (env);
   mod_path (dirname, &exec_path);
-  set_in_environ (current_inferior ()->environment, path_var_name, exec_path);
+  current_inferior ()->environment.set (path_var_name, exec_path);
   xfree (exec_path);
   if (from_tty)
     path_info ((char *) NULL, from_tty);
diff --git a/gdb/inferior.c b/gdb/inferior.c
index 0b655f4..9fa2dad 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -81,7 +81,6 @@ inferior::~inferior ()
   inferior_free_data (inf);
   xfree (inf->args);
   xfree (inf->terminal);
-  free_environ (inf->environment);
   target_desc_info_free (inf->tdesc_info);
   xfree (inf->priv);
 }
@@ -89,10 +88,9 @@ inferior::~inferior ()
 inferior::inferior (int pid_)
   : num (++highest_inferior_num),
     pid (pid_),
-    environment (make_environ ()),
+    environment (gdb_environ::from_host_environ ()),
     registry_data ()
 {
-  init_environ (this->environment);
   inferior_alloc_data (this);
 }
 
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 1c541b7..8ada4f8 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -30,7 +30,6 @@ struct regcache;
 struct ui_out;
 struct terminal_info;
 struct target_desc_info;
-struct gdb_environ;
 struct continuation;
 struct inferior;
 
@@ -43,6 +42,9 @@ struct inferior;
 /* For struct frame_id.  */
 #include "frame.h"
 
+/* For gdb_environ.  */
+#include "environ.h"
+
 #include "progspace.h"
 #include "registry.h"
 
@@ -363,7 +365,7 @@ public:
 
   /* Environment to use for running inferior,
      in format described in environ.h.  */
-  gdb_environ *environment = NULL;
+  gdb_environ environment;
 
   /* True if this child process was attached rather than forked.  */
   bool attach_flag = false;
diff --git a/gdb/mi/mi-cmd-env.c b/gdb/mi/mi-cmd-env.c
index 97be139..bf4578c 100644
--- a/gdb/mi/mi-cmd-env.c
+++ b/gdb/mi/mi-cmd-env.c
@@ -167,7 +167,7 @@ mi_cmd_env_path (const char *command, char **argv, int argc)
   else
     {
       /* Otherwise, get current path to modify.  */
-      env = get_in_environ (current_inferior ()->environment, path_var_name);
+      env = current_inferior ()->environment.get (path_var_name);
 
       /* Can be null if path is not set.  */
       if (!env)
@@ -178,9 +178,9 @@ mi_cmd_env_path (const char *command, char **argv, int argc)
   for (i = argc - 1; i >= 0; --i)
     env_mod_path (argv[i], &exec_path);
 
-  set_in_environ (current_inferior ()->environment, path_var_name, exec_path);
+  current_inferior ()->environment.set (path_var_name, exec_path);
   xfree (exec_path);
-  env = get_in_environ (current_inferior ()->environment, path_var_name);
+  env = current_inferior ()->environment.get (path_var_name);
   uiout->field_string ("path", env);
 }
 
diff --git a/gdb/solib.c b/gdb/solib.c
index 491c18a..788cf15 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -350,16 +350,15 @@ solib_find_1 (const char *in_pathname, int *fd, int is_solib)
 
   /* If not found, next search the inferior's $PATH environment variable.  */
   if (found_file < 0 && sysroot == NULL)
-    found_file = openp (get_in_environ (current_inferior ()->environment,
-					"PATH"),
+    found_file = openp (current_inferior ()->environment.get ("PATH"),
 			OPF_TRY_CWD_FIRST | OPF_RETURN_REALPATH, in_pathname,
 			O_RDONLY | O_BINARY, &temp_pathname);
 
   /* If not found, and we're looking for a solib, next search the
      inferior's $LD_LIBRARY_PATH environment variable.  */
   if (is_solib && found_file < 0 && sysroot == NULL)
-    found_file = openp (get_in_environ (current_inferior ()->environment,
-					"LD_LIBRARY_PATH"),
+    found_file = openp (current_inferior ()->environment.get
+			("LD_LIBRARY_PATH"),
 			OPF_TRY_CWD_FIRST | OPF_RETURN_REALPATH, in_pathname,
 			O_RDONLY | O_BINARY, &temp_pathname);
 
diff --git a/gdb/unittests/environ-selftests.c b/gdb/unittests/environ-selftests.c
new file mode 100644
index 0000000..ecc3955
--- /dev/null
+++ b/gdb/unittests/environ-selftests.c
@@ -0,0 +1,151 @@
+/* Self tests for gdb_environ for GDB, the GNU debugger.
+
+   Copyright (C) 2017 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "selftest.h"
+#include "common/environ.h"
+
+namespace selftests {
+namespace gdb_environ_tests {
+
+static void
+run_tests ()
+{
+  /* Set a test environment variable.  This will be unset at the end
+     of this function.  */
+  if (setenv ("GDB_SELFTEST_ENVIRON", "1", 1) != 0)
+    error (_("Could not set environment variable for testing."));
+
+  gdb_environ env;
+
+  /* When the vector is initialized, there should always be one NULL
+     element in it.  */
+  SELF_CHECK (env.envp ()[0] == NULL);
+
+  /* Make sure that there is no other element.  */
+  SELF_CHECK (env.get ("PWD") == NULL);
+
+  /* Check if unset followed by a set in an empty vector works.  */
+  env.set ("PWD", "test");
+  SELF_CHECK (strcmp (env.get ("PWD"), "test") == 0);
+  /* The second element must be NULL.  */
+  SELF_CHECK (env.envp ()[1] == NULL);
+  env.unset ("PWD");
+  SELF_CHECK (env.envp ()[0] == NULL);
+
+  /* Initialize the environment vector using the host's environ.  */
+  env = gdb_environ::from_host_environ ();
+
+  /* Our test environment variable should be present at the
+     vector.  */
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0);
+
+  /* Set our test variable to another value.  */
+  env.set ("GDB_SELFTEST_ENVIRON", "test");
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "test") == 0);
+
+  /* And unset our test variable.  The variable still exists in the
+     host's environment, but doesn't exist in our vector.  */
+  env.unset ("GDB_SELFTEST_ENVIRON");
+  SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL);
+
+  /* Re-set the test variable.  */
+  env.set ("GDB_SELFTEST_ENVIRON", "1");
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON"), "1") == 0);
+
+  /* When we clear our environ vector, there should be only one
+     element on it (NULL), and we shouldn't be able to get our test
+     variable.  */
+  env.clear ();
+  SELF_CHECK (env.envp ()[0] == NULL);
+  SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON") == NULL);
+
+  /* Reinitialize our environ vector using the host environ.  We
+     should be able to see one (and only one) instance of the test
+     variable.  */
+  env = gdb_environ::from_host_environ ();
+  char **envp = env.envp ();
+  int num_found = 0;
+
+  for (size_t i = 0; envp[i] != NULL; ++i)
+    if (strcmp (envp[i], "GDB_SELFTEST_ENVIRON=1") == 0)
+      ++num_found;
+  SELF_CHECK (num_found == 1);
+
+  /* Get rid of our test variable.  */
+  unsetenv ("GDB_SELFTEST_ENVIRON");
+
+  /* Test the case when we set a variable A, then set a variable B,
+     then unset A, and make sure that we cannot find A in the environ
+     vector, but can still find B.  */
+  env.set ("GDB_SELFTEST_ENVIRON_1", "aaa");
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON_1"), "aaa") == 0);
+
+  env.set ("GDB_SELFTEST_ENVIRON_2", "bbb");
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON_2"), "bbb") == 0);
+
+  env.unset ("GDB_SELFTEST_ENVIRON_1");
+  SELF_CHECK (env.get ("GDB_SELFTEST_ENVIRON_1") == NULL);
+  SELF_CHECK (strcmp (env.get ("GDB_SELFTEST_ENVIRON_2"), "bbb") == 0);
+
+  env.clear ();
+
+  /* Test that after a std::move the moved-from object is left at a
+     valid state (i.e., its only element is NULL).  */
+  env.set ("A", "1");
+  SELF_CHECK (strcmp (env.get ("A"), "1") == 0);
+  gdb_environ env2;
+  env2 = std::move (env);
+  SELF_CHECK (env.envp ()[0] == NULL);
+  SELF_CHECK (strcmp (env2.get ("A"), "1") == 0);
+  SELF_CHECK (env2.envp ()[1] == NULL);
+  env.set ("B", "2");
+  SELF_CHECK (strcmp (env.get ("B"), "2") == 0);
+  SELF_CHECK (env.envp ()[1] == NULL);
+
+  /* Test that the move constructor leaves everything at a valid
+     state.  */
+  env.clear ();
+  env.set ("A", "1");
+  SELF_CHECK (strcmp (env.get ("A"), "1") == 0);
+  gdb_environ env3 = std::move (env);
+  SELF_CHECK (env.envp ()[0] == NULL);
+  SELF_CHECK (strcmp (env3.get ("A"), "1") == 0);
+  SELF_CHECK (env3.envp ()[1] == NULL);
+  env.set ("B", "2");
+  SELF_CHECK (strcmp (env.get ("B"), "2") == 0);
+  SELF_CHECK (env.envp ()[1] == NULL);
+
+  /* Test self-move.  */
+  env.clear ();
+  env.set ("A", "1");
+  SELF_CHECK (strcmp (env.get ("A"), "1") == 0);
+  env = std::move (env);
+  SELF_CHECK (strcmp (env.get ("A"), "1") == 0);
+  SELF_CHECK (strcmp (env.envp ()[0], "A=1") == 0);
+  SELF_CHECK (env.envp ()[1] == NULL);
+}
+} /* namespace gdb_environ */
+} /* namespace selftests */
+
+void
+_initialize_environ_selftests ()
+{
+  register_self_test (selftests::gdb_environ_tests::run_tests);
+}
-- 
2.9.3

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v8] C++ify gdb/common/environ.c
  2017-06-20  3:27 ` [PATCH v8] " Sergio Durigan Junior
@ 2017-06-20 12:13   ` Pedro Alves
  2017-06-20 12:46   ` Simon Marchi
  1 sibling, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2017-06-20 12:13 UTC (permalink / raw)
  To: Sergio Durigan Junior, GDB Patches; +Cc: Simon Marchi

This version is fine with me.  Simon?

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v8] C++ify gdb/common/environ.c
  2017-06-20  3:27 ` [PATCH v8] " Sergio Durigan Junior
  2017-06-20 12:13   ` Pedro Alves
@ 2017-06-20 12:46   ` Simon Marchi
  2017-06-20 13:00     ` Sergio Durigan Junior
  1 sibling, 1 reply; 47+ messages in thread
From: Simon Marchi @ 2017-06-20 12:46 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: GDB Patches, Pedro Alves

On 2017-06-20 05:27, Sergio Durigan Junior wrote:
> +/* Create a gdb_environ object using the host's environment
> +   variables.  */

This comment (about from_host_environ) should probably go in the header 
file, with a /* See common/environ.h.  */ here.

> +  /* Return the value in the environment for the variable VAR.  The
> +     return pointer is only valid as long as VAR is not

return -> returned?

> +     removed/replaced from the environment.  */

What if another variable is set and it causes reallocation of the 
vector?  We could be safe and say "as long as the gdb_environ object is 
not modified".

Otherwise, LGTM.

Simon

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v8] C++ify gdb/common/environ.c
  2017-06-20 12:46   ` Simon Marchi
@ 2017-06-20 13:00     ` Sergio Durigan Junior
  0 siblings, 0 replies; 47+ messages in thread
From: Sergio Durigan Junior @ 2017-06-20 13:00 UTC (permalink / raw)
  To: Simon Marchi; +Cc: GDB Patches, Pedro Alves

On Tuesday, June 20 2017, Simon Marchi wrote:

> On 2017-06-20 05:27, Sergio Durigan Junior wrote:
>> +/* Create a gdb_environ object using the host's environment
>> +   variables.  */
>
> This comment (about from_host_environ) should probably go in the
> header file, with a /* See common/environ.h.  */ here.

Fixed.

>> +  /* Return the value in the environment for the variable VAR.  The
>> +     return pointer is only valid as long as VAR is not
>
> return -> returned?

Fixed.

>> +     removed/replaced from the environment.  */
>
> What if another variable is set and it causes reallocation of the
> vector?  We could be safe and say "as long as the gdb_environ object
> is not modified".

Fixed.

> Otherwise, LGTM.

Thanks to both of you for the valuable comments.

Pushed.

9a6c7d9c021cfeb290d76584db7a01e57e7c3d4e

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v5] C++ify gdb/common/environ.c
  2017-06-19 12:13     ` Pedro Alves
@ 2017-06-20 14:02       ` Pedro Alves
  0 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2017-06-20 14:02 UTC (permalink / raw)
  To: Simon Marchi, Sergio Durigan Junior; +Cc: GDB Patches

On 06/19/2017 01:13 PM, Pedro Alves wrote:
> On 06/17/2017 09:54 AM, Simon Marchi wrote:
> 
>> I actually preferred the option of adding the NULL element to the vector
>> in the gdb_environ constructor, since it allows always having the vector
>> in a consistent state.  I don't think that avoiding that heap allocation
>> is worth the complexity it adds to the code (unless we can prove
>> otherwise by memory usage profiling).
> 
> I'm not exactly sure what complexity this is, but I'm not going to
> strongly object to always putting in the NULL element, since that's
> what we currently do today.  This shows we're missing unit test coverage
> at least.

Now that the patch is in and we have better unit tests, here's what the
alternative looks like.  IMO, the level of complexity is equivalent.
It's a little more complicated on a couple places, and a little simpler
on others.

From d3d4aea4ce0ce3a64008b56664feb68635acadb8 Mon Sep 17 00:00:00 2001
From: Pedro Alves <palves@redhat.com>
Date: Tue, 20 Jun 2017 14:38:48 +0100
Subject: [PATCH] empty environs

---
 gdb/common/environ.c | 41 ++++++++++++++++++++++++++---------------
 gdb/common/environ.h | 21 ++++++---------------
 2 files changed, 32 insertions(+), 30 deletions(-)

diff --git a/gdb/common/environ.c b/gdb/common/environ.c
index 2d13957..c2d0d95 100644
--- a/gdb/common/environ.c
+++ b/gdb/common/environ.c
@@ -30,8 +30,6 @@ gdb_environ::operator= (gdb_environ &&e)
     return *this;
 
   m_environ_vector = std::move (e.m_environ_vector);
-  e.m_environ_vector.clear ();
-  e.m_environ_vector.push_back (NULL);
   return *this;
 }
 
@@ -42,15 +40,12 @@ gdb_environ gdb_environ::from_host_environ ()
   extern char **environ;
   gdb_environ e;
 
-  if (environ == NULL)
+  if (environ == NULL || environ[0] == NULL)
     return e;
 
   for (int i = 0; environ[i] != NULL; ++i)
-    {
-      /* Make sure we add the element before the last (NULL).  */
-      e.m_environ_vector.insert (e.m_environ_vector.end () - 1,
-				 xstrdup (environ[i]));
-    }
+    e.m_environ_vector.push_back (xstrdup (environ[i]));
+  e.m_environ_vector.push_back (NULL);
 
   return e;
 }
@@ -63,8 +58,6 @@ gdb_environ::clear ()
   for (char *v : m_environ_vector)
     xfree (v);
   m_environ_vector.clear ();
-  /* Always add the NULL element.  */
-  m_environ_vector.push_back (NULL);
 }
 
 /* Helper function to check if STRING contains an environment variable
@@ -99,10 +92,18 @@ gdb_environ::get (const char *var) const
 void
 gdb_environ::set (const char *var, const char *value)
 {
-  /* We have to unset the variable in the vector if it exists.  */
+  if (m_environ_vector.empty ())
+    {
+      m_environ_vector.push_back (concat (var, "=", value, NULL));
+      m_environ_vector.push_back (NULL);
+      return;
+    }
+
+  /* Unset the variable in the vector if it exists.  */
   unset (var);
 
-  /* Insert the element before the last one, which is always NULL.  */
+  /* Insert the element before the last one, which is the NULL
+     terminator.  */
   m_environ_vector.insert (m_environ_vector.end () - 1,
 			   concat (var, "=", value, NULL));
 }
@@ -112,10 +113,13 @@ gdb_environ::set (const char *var, const char *value)
 void
 gdb_environ::unset (const char *var)
 {
+  if (m_environ_vector.empty ())
+    return;
+
   size_t len = strlen (var);
 
-  /* We iterate until '.cend () - 1' because the last element is
-     always NULL.  */
+  /* We iterate until '.end () - 1' because the last element is the
+     NULL terminator.  */
   for (std::vector<char *>::iterator el = m_environ_vector.begin ();
        el != m_environ_vector.end () - 1;
        ++el)
@@ -127,10 +131,17 @@ gdb_environ::unset (const char *var)
       }
 }
 
+/* An empty envp.  This is what the envp() method returns when the
+   internal vector is empty.  */
+static const char *const empty_envp[1] = { NULL };
+
 /* See common/environ.h.  */
 
 char **
 gdb_environ::envp () const
 {
-  return const_cast<char **> (&m_environ_vector[0]);
+  const char *const *e = (m_environ_vector.empty ()
+			  ? empty_envp
+			  : &m_environ_vector[0]);
+  return const_cast<char **> (e);
 }
diff --git a/gdb/common/environ.h b/gdb/common/environ.h
index 0bbb191..1a5494b 100644
--- a/gdb/common/environ.h
+++ b/gdb/common/environ.h
@@ -25,14 +25,7 @@
 class gdb_environ
 {
 public:
-  /* Regular constructor and destructor.  */
-  gdb_environ ()
-  {
-    /* Make sure that the vector contains at least a NULL element.
-       If/when we add more variables to it, NULL will always be the
-       last element.  */
-    m_environ_vector.push_back (NULL);
-  }
+  gdb_environ () = default;
 
   ~gdb_environ ()
   {
@@ -42,12 +35,7 @@ public:
   /* Move constructor.  */
   gdb_environ (gdb_environ &&e)
     : m_environ_vector (std::move (e.m_environ_vector))
-  {
-    /* Make sure that the moved-from vector is left at a valid
-       state (only one NULL element).  */
-    e.m_environ_vector.clear ();
-    e.m_environ_vector.push_back (NULL);
-  }
+  {}
 
   /* Move assignment.  */
   gdb_environ &operator= (gdb_environ &&e);
@@ -74,7 +62,10 @@ public:
   char **envp () const;
 
 private:
-  /* A vector containing the environment variables.  */
+  /* A vector containing the environment variables.  An initially
+     empty envp is internally represented with an empty vector to
+     avoid having to push a NULL (which heap-allocates) on default
+     construction and on moved-from objects.  */
   std::vector<char *> m_environ_vector;
 };
 
-- 
2.5.5


^ permalink raw reply	[flat|nested] 47+ messages in thread

end of thread, other threads:[~2017-06-20 14:02 UTC | newest]

Thread overview: 47+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-04-13  4:05 [PATCH] C++ify gdb/common/environ.c Sergio Durigan Junior
2017-04-15 18:51 ` [PATCH v2] " Sergio Durigan Junior
2017-04-15 21:22   ` Simon Marchi
2017-04-18  2:49     ` Sergio Durigan Junior
2017-04-16  5:09   ` Simon Marchi
2017-04-16 17:32     ` Sergio Durigan Junior
2017-04-18  3:03 ` [PATCH v3] " Sergio Durigan Junior
2017-04-19  4:56   ` Simon Marchi
2017-04-19 16:30     ` Pedro Alves
2017-04-19 18:14   ` Pedro Alves
2017-05-01  2:22     ` Sergio Durigan Junior
2017-05-04 15:30       ` Pedro Alves
2017-06-14 19:22 ` [PATCH v4] " Sergio Durigan Junior
2017-06-16 15:45   ` Pedro Alves
2017-06-16 18:01     ` Sergio Durigan Junior
2017-06-16 18:23       ` Pedro Alves
2017-06-16 21:59         ` Sergio Durigan Junior
2017-06-16 22:23 ` [PATCH v5] " Sergio Durigan Junior
2017-06-17  8:54   ` Simon Marchi
2017-06-19  4:19     ` Sergio Durigan Junior
2017-06-19 13:40       ` Pedro Alves
2017-06-19 16:19         ` Sergio Durigan Junior
2017-06-19 12:13     ` Pedro Alves
2017-06-20 14:02       ` Pedro Alves
2017-06-19  4:36 ` [PATCH v6] " Sergio Durigan Junior
2017-06-19  4:51   ` Sergio Durigan Junior
2017-06-19  7:18     ` Simon Marchi
2017-06-19 14:26       ` Pedro Alves
2017-06-19 15:30         ` Simon Marchi
2017-06-19 15:44           ` Pedro Alves
2017-06-19 15:47             ` Pedro Alves
2017-06-19 16:26             ` Simon Marchi
2017-06-19 16:55               ` Pedro Alves
2017-06-19 17:59                 ` Sergio Durigan Junior
2017-06-19 18:09                   ` Pedro Alves
2017-06-19 18:23                     ` Sergio Durigan Junior
2017-06-19 18:36                       ` Pedro Alves
2017-06-19 18:38                         ` Pedro Alves
2017-06-19 14:26   ` Pedro Alves
2017-06-19 16:13     ` Sergio Durigan Junior
2017-06-19 16:38       ` Pedro Alves
2017-06-19 16:46         ` Sergio Durigan Junior
2017-06-19 18:27 ` [PATCH v7] " Sergio Durigan Junior
2017-06-20  3:27 ` [PATCH v8] " Sergio Durigan Junior
2017-06-20 12:13   ` Pedro Alves
2017-06-20 12:46   ` Simon Marchi
2017-06-20 13:00     ` Sergio Durigan Junior

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).