From c5dd3efd0a647e11fd82a7fa30b421feef752924 Mon Sep 17 00:00:00 2001
From: Michael Livshin <repo@cmm.kakpryg.net>
Date: Fri, 28 Dec 2018 21:24:42 +0200
Subject: [PATCH] Print backtrace on fatal error

Also add built-in "backtrace" function.
---
 src/function.c |  19 ++++-
 src/makeint.h  |   1 +
 src/output.c   | 223 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/variable.c |  12 ++-
 src/variable.h |  14 +++-
 5 files changed, 261 insertions(+), 8 deletions(-)

diff --git a/src/function.c b/src/function.c
index 4602ebb..f308010 100644
--- a/src/function.c
+++ b/src/function.c
@@ -878,7 +878,7 @@ func_foreach (char *o, char **argv, const char *funcname UNUSED)
   char *vp = next_token (varname);
   end_of_token (vp)[0] = '\0';
 
-  push_new_variable_scope ();
+  push_new_variable_scope (SCOPE_OTHER, "foreach");
   var = define_variable (vp, strlen (vp), "", o_automatic, 0);
 
   /* loop through LIST,  put the value in VAR and expand BODY */
@@ -1158,6 +1158,20 @@ func_error (char *o, char **argv, const char *funcname)
 }
 
 
+/*
+  Print backtrace.
+*/
+static char *
+func_backtrace (char *o, char **argv UNUSED, const char *funcname UNUSED)
+{
+  const char *header = argv[0];
+  const char *s = argv[1];
+  if (s != NULL)
+    NEXT_TOKEN (s);
+  backtrace (header, s != NULL);
+  return o;
+}
+
 /*
   chop argv[0] into words, and sort them.
  */
@@ -2364,6 +2378,7 @@ static struct function_table_entry function_table_init[] =
   FT_ENTRY ("info",          0,  1,  1,  func_error),
   FT_ENTRY ("error",         0,  1,  1,  func_error),
   FT_ENTRY ("warning",       0,  1,  1,  func_error),
+  FT_ENTRY ("backtrace",     1,  2,  1,  func_backtrace),
   FT_ENTRY ("if",            2,  3,  0,  func_if),
   FT_ENTRY ("or",            1,  0,  0,  func_or),
   FT_ENTRY ("and",           1,  0,  0,  func_and),
@@ -2597,7 +2612,7 @@ func_call (char *o, char **argv, const char *funcname UNUSED)
 
   /* Set up arguments $(1) .. $(N).  $(0) is the function name.  */
 
-  push_new_variable_scope ();
+  push_new_variable_scope (SCOPE_FUNCTION, NULL);
 
   for (i=0; *argv; ++i, ++argv)
     {
diff --git a/src/makeint.h b/src/makeint.h
index 315500f..988351d 100644
--- a/src/makeint.h
+++ b/src/makeint.h
@@ -493,6 +493,7 @@ void error (const floc *flocp, size_t length, const char *fmt, ...)
 void fatal (const floc *flocp, size_t length, const char *fmt, ...)
             __attribute__ ((noreturn, __format__ (__printf__, 3, 4)));
 void out_of_memory () __attribute__((noreturn));
+void backtrace (const char *header, int is_err);
 
 /* When adding macros to this list be sure to update the value of
    XGETTEXT_OPTIONS in the po/Makevars file.  */
diff --git a/src/output.c b/src/output.c
index 9a58b9f..09511d6 100644
--- a/src/output.c
+++ b/src/output.c
@@ -17,6 +17,7 @@ this program.  If not, see <http://www.gnu.org/licenses/>.  */
 #include "makeint.h"
 #include "os.h"
 #include "output.h"
+#include "variable.h"
 
 /* GNU make no longer supports pre-ANSI89 environments.  */
 
@@ -584,6 +585,226 @@ message (int prefix, size_t len, const char *fmt, ...)
   outputs (0, fmtbuf.buffer);
 }
 
+#define BACKTRACE_MAX_VALUE_LENGTH 50
+#define BACKTRACE_VALUE_MIDDLE_FILLER "<...>"
+
+static size_t
+_backtrace_value_length (const char *value)
+{
+  return MIN (strlen (value), BACKTRACE_MAX_VALUE_LENGTH);
+}
+
+static void
+_backtrace_sprint_value (char **p, const char *value)
+{
+  size_t l = strlen (value);
+
+  if (l <= BACKTRACE_MAX_VALUE_LENGTH)
+    strcpy (*p, value);
+  else {
+    static const size_t filler_length = strlen (BACKTRACE_VALUE_MIDDLE_FILLER);
+    size_t left = (BACKTRACE_MAX_VALUE_LENGTH - filler_length) / 2;
+    size_t right = BACKTRACE_MAX_VALUE_LENGTH - left - filler_length;
+    strncpy (*p, value, left);
+    *p += left;
+    strcpy (*p, BACKTRACE_VALUE_MIDDLE_FILLER);
+    *p += filler_length;
+    strcpy (*p, value + l - right);
+  }
+
+  *p += strlen (*p);
+}
+
+struct _backtrace_scope_logic;
+typedef void (*_backtrace_var_op_t) (const struct variable *var,
+                                     const struct _backtrace_scope_logic *logic,
+                                     int is_first, void *arg);
+typedef void (*_backtrace_scope_var_mapper_t) (const struct variable_set *set,
+                                               const struct _backtrace_scope_logic *logic,
+                                               _backtrace_var_op_t var_op,
+                                               void *arg);
+struct _backtrace_scope_logic {
+  const char *fmt;
+  const char *suffix;
+  _backtrace_scope_var_mapper_t map_vars;
+  const char *name_var;
+  int print_var_names;
+};
+
+static void
+_backtrace_var_count (const struct variable *var,
+                      const struct _backtrace_scope_logic *logic,
+                      int is_first, void *arg)
+{
+  size_t *len = arg;
+
+  if (!is_first)
+    (*len)++;
+  if (logic->print_var_names)
+    *len += strlen (var->name) + 1;
+  *len += _backtrace_value_length (var->value);
+}
+
+static void
+_backtrace_var_print (const struct variable *var,
+                      const struct _backtrace_scope_logic *logic,
+                      int is_first, void *arg)
+{
+  char **p = arg;
+
+  if (!is_first)
+    strcpy ((*p)++, ",");
+  if (logic->print_var_names)
+    sprintf (*p, "%s=", var->name);
+  (*p) += strlen (*p);
+  _backtrace_sprint_value (p, var->value);
+}
+
+static const char *
+_backtrace_scope_name (const struct variable_set *set, const struct _backtrace_scope_logic *logic)
+{
+  const char *res = "<unknown>";
+
+  if (logic->name_var) {
+    struct variable var_key;
+    struct variable *var;
+    var_key.name = (char *)logic->name_var;
+    var_key.length = strlen (logic->name_var);
+    var = hash_find_item ((struct hash_table *)&set->table, &var_key);
+    if (var)
+      res = var->value;
+  } else {
+    if (set->scope_name)
+      res = set->scope_name;
+  }
+
+  return res;
+}
+
+static void
+_backtrace_scope_count (const struct variable_set *set, const struct _backtrace_scope_logic *logic,
+                        size_t *len)
+{
+  /* minus 2 for the %s in fmt, plus 1 for the newline at the end */
+  *len += strlen (logic->fmt) + strlen (_backtrace_scope_name (set, logic)) +
+          strlen (logic->suffix) - 1;
+
+  logic->map_vars (set, logic, _backtrace_var_count, len);
+}
+
+static void
+_backtrace_scope_print (const struct variable_set *set, const struct _backtrace_scope_logic *logic,
+                        char **p)
+{
+  sprintf (*p, logic->fmt, _backtrace_scope_name (set, logic));
+  *p += strlen (*p);
+
+  logic->map_vars (set, logic, _backtrace_var_print, p);
+
+  sprintf (*p, "%s\n", logic->suffix);
+  *p += strlen (*p);
+}
+
+static void
+_backtrace_map_args (const struct variable_set *set,
+                     const struct _backtrace_scope_logic *logic,
+                     _backtrace_var_op_t var_op,
+                     void *arg)
+{
+  int i;
+  int is_first;
+
+  for (i = 1, is_first = 1; ; i++, is_first = 0) {
+    char numbuf[32];
+    struct variable var_key;
+    const struct variable *param;
+    var_key.name = numbuf;
+
+    sprintf (var_key.name, "%d", i);
+    var_key.length = strlen (var_key.name);
+    param = hash_find_item ((struct hash_table *)&set->table, &var_key);
+    if (!param)
+      break;
+
+    (*var_op) (param, logic, is_first, arg);
+  }
+}
+
+struct _backtrace_map_var_state {
+  const struct _backtrace_scope_logic *logic;
+  _backtrace_var_op_t op;
+  int is_first;
+  void *arg;
+};
+
+static void
+_backtrace_map_vars_op (const void *item, void *arg)
+{
+  const struct variable *var = item;
+  struct _backtrace_map_var_state *state = arg;
+
+  if (state->logic->name_var && !strcmp (state->logic->name_var, var->name))
+    return;
+
+  (*state->op) (var, state->logic, state->is_first, state->arg);
+  state->is_first = 0;
+}
+
+static void
+_backtrace_map_vars (const struct variable_set *set,
+                     const struct _backtrace_scope_logic *logic,
+                     _backtrace_var_op_t var_op,
+                     void *arg)
+{
+  struct _backtrace_map_var_state state = { logic, var_op, 1, arg };
+  hash_map_arg ((struct hash_table *)&set->table, _backtrace_map_vars_op, &state);
+}
+
+static const struct _backtrace_scope_logic *
+_backtrace_scope_logic (enum scope_type type)
+{
+  static const struct _backtrace_scope_logic function_logic =
+    { "$(call %s,", ")", _backtrace_map_args, "0", 0 };
+  static const struct _backtrace_scope_logic target_logic =
+    { "%s: ", "", _backtrace_map_vars, "@", 1 };
+  static const struct _backtrace_scope_logic other_logic =
+    { "%s{", "}", _backtrace_map_vars, NULL, 1 };
+
+  switch (type) {
+  case SCOPE_FUNCTION:
+    return &function_logic;
+  case SCOPE_TARGET:
+    return &target_logic;
+  default:
+    return &other_logic;
+  }
+}
+
+void
+backtrace (const char *header, int is_err)
+{
+  struct variable_set_list *sl;
+  size_t len = 0;
+  char *p;
+
+  if (current_variable_set_list->next == NULL)
+    return;
+
+  len += strlen (header) + 2;
+  for (sl = current_variable_set_list; sl->next != NULL; sl = sl->next)
+    _backtrace_scope_count (sl->set, _backtrace_scope_logic (sl->set->scope_type), &len);
+
+  p = get_buffer (len);
+  sprintf (p, "%s:\n", header);
+  p += strlen (p);
+
+  for (sl = current_variable_set_list; sl->next != NULL; sl = sl->next)
+    _backtrace_scope_print (sl->set, _backtrace_scope_logic (sl->set->scope_type), &p);
+
+  outputs (is_err, fmtbuf.buffer);
+}
+
+
 /* Print an error message.  */
 
 void
@@ -646,6 +867,8 @@ fatal (const floc *flocp, size_t len, const char *fmt, ...)
   assert (fmtbuf.buffer[len-1] == '\0');
   outputs (1, fmtbuf.buffer);
 
+  backtrace ("In", 1);
+
   die (MAKE_FAILURE);
 }
 
diff --git a/src/variable.c b/src/variable.c
index 11acbdf..194049f 100644
--- a/src/variable.c
+++ b/src/variable.c
@@ -577,6 +577,8 @@ initialize_file_variables (struct file *file, int reading)
       l = (struct variable_set_list *)
         xmalloc (sizeof (struct variable_set_list));
       l->set = xmalloc (sizeof (struct variable_set));
+      l->set->scope_type = SCOPE_TARGET;
+      l->set->scope_name = NULL;
       hash_init (&l->set->table, PERFILE_VARIABLE_BUCKETS,
                  variable_hash_1, variable_hash_2, variable_hash_cmp);
       file->variables = l;
@@ -618,7 +620,7 @@ initialize_file_variables (struct file *file, int reading)
           /* We found at least one.  Set up a new variable set to accumulate
              all the pattern variables that match this target.  */
 
-          file->pat_variables = create_new_variable_set ();
+          file->pat_variables = create_new_variable_set (SCOPE_TARGET, NULL);
           current_variable_set_list = file->pat_variables;
 
           do
@@ -671,12 +673,14 @@ initialize_file_variables (struct file *file, int reading)
    and free all its storage.  */
 
 struct variable_set_list *
-create_new_variable_set (void)
+create_new_variable_set (enum scope_type type, const char *scope_name)
 {
   struct variable_set_list *setlist;
   struct variable_set *set;
 
   set = xmalloc (sizeof (struct variable_set));
+  set->scope_type = type;
+  set->scope_name = scope_name;
   hash_init (&set->table, SMALL_SCOPE_VARIABLE_BUCKETS,
              variable_hash_1, variable_hash_2, variable_hash_cmp);
 
@@ -696,9 +700,9 @@ create_new_variable_set (void)
  */
 
 struct variable_set_list *
-push_new_variable_scope (void)
+push_new_variable_scope (enum scope_type type, const char *scope_name)
 {
-  current_variable_set_list = create_new_variable_set ();
+  current_variable_set_list = create_new_variable_set (type, scope_name);
   if (current_variable_set_list->next == &global_setlist)
     {
       /* It was the global, so instead of new -> &global we want to replace
diff --git a/src/variable.h b/src/variable.h
index 43da96a..7f24709 100644
--- a/src/variable.h
+++ b/src/variable.h
@@ -83,8 +83,16 @@ struct variable
 
 /* Structure that represents a variable set.  */
 
+enum scope_type {
+      SCOPE_FUNCTION,
+      SCOPE_TARGET,
+      SCOPE_OTHER, /* needs a name */
+};
+
 struct variable_set
   {
+    enum scope_type scope_type;
+    const char *scope_name;
     struct hash_table table;    /* Hash table of variables.  */
   };
 
@@ -118,6 +126,8 @@ extern struct variable shell_var;
 # define SIZE_MAX ((size_t)~(size_t)0)
 #endif
 
+struct file;
+
 char *variable_buffer_output (char *ptr, const char *string, size_t length);
 char *variable_expand (const char *line);
 char *variable_expand_for_file (const char *line, struct file *file);
@@ -147,9 +157,9 @@ char *recursively_expand_for_file (struct variable *v, struct file *file);
 #define recursively_expand(v)   recursively_expand_for_file (v, NULL)
 
 /* variable.c */
-struct variable_set_list *create_new_variable_set (void);
+struct variable_set_list *create_new_variable_set (enum scope_type type, const char *scope_name);
 void free_variable_set (struct variable_set_list *);
-struct variable_set_list *push_new_variable_scope (void);
+struct variable_set_list *push_new_variable_scope (enum scope_type type, const char *scope_name);
 void pop_variable_scope (void);
 void define_automatic_variables (void);
 void initialize_file_variables (struct file *file, int reading);
-- 
2.19.2

