From e78ecf478076a0e6bd756d7d5d0244bab7ba0214 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   | 202 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/variable.c |  12 ++-
 src/variable.h |  14 +++-
 5 files changed, 240 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..bcbe1df 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,205 @@ message (int prefix, size_t len, const char *fmt, ...)
   outputs (0, fmtbuf.buffer);
 }
 
+#define BACKTRACE_MAX_VALUE_LENGTH 50
+
+enum _backtrace_skip {
+      SKIP_NONE,
+      SKIP_AT,
+};
+struct _backtrace_count_state {
+  size_t len;
+  int is_first;
+  enum _backtrace_skip skip;
+};
+struct _backtrace_print_state {
+  char *p;
+  int is_first;
+  enum _backtrace_skip skip;
+};
+
+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 {
+    size_t left = BACKTRACE_MAX_VALUE_LENGTH / 2 - 2;
+    size_t right = BACKTRACE_MAX_VALUE_LENGTH - left - 3;
+    strncpy (p, value, left);
+    p += left;
+    strcpy (p, "[*]");
+    p += 3;
+    strncpy (p, value + l - right, right);
+    p[right] = '\0';
+    p += right;
+  }
+}
+
+static void
+_backtrace_scope_count (const void *item, void *arg)
+{
+  const struct variable *var = item;
+  struct _backtrace_count_state *cs = arg;
+
+  if (cs->skip == SKIP_AT && var->name[0] == '@' && var->name[1] == '\0')
+    return;
+
+  cs->len += strlen (var->name) + 1 + _backtrace_value_length (var->value);
+  if (cs->is_first)
+    cs->is_first = 0;
+  else
+    cs->len++;
+}
+
+static void
+_backtrace_scope_print (const void *item, void *arg)
+{
+  const struct variable *var = item;
+  struct _backtrace_print_state *ps = arg;
+
+  if (ps->skip == SKIP_AT && var->name[0] == '@' && var->name[1] == '\0')
+    return;
+
+  if (ps->is_first)
+    ps->is_first = 0;
+  else {
+    sprintf (ps->p, ",");
+    ps->p++;
+  }
+
+  sprintf (ps->p, "%s=", var->name);
+  ps->p += strlen (ps->p);
+  _backtrace_sprint_value (ps->p, var->value);
+}
+
+void
+backtrace (const char *header, int is_err)
+{
+  struct variable_set_list *sl;
+  struct _backtrace_count_state cs = { .len = 0 };
+  struct _backtrace_print_state ps;
+  char numbuf[32];
+  struct variable var_key = { .name = numbuf };
+  const struct variable *name, *param;
+  int i;
+
+  if (current_variable_set_list->next == NULL)
+    return;
+
+  cs.len += strlen (header) + 2;
+  for (sl = current_variable_set_list; sl->next != NULL; sl = sl->next) {
+
+    switch (sl->set->scope_type) {
+    case SCOPE_FUNCTION:
+      var_key.name[0] = '0';
+      var_key.length = 1;
+      name = hash_find_item (&sl->set->table, &var_key);
+      cs.len += strlen (name->value) + 10;
+
+      for (cs.is_first = 1, i = 1;; cs.is_first = 0, i++) {
+        sprintf (var_key.name, "%d", i);
+        var_key.length = strlen (var_key.name);
+        param = hash_find_item (&sl->set->table, &var_key);
+        if (!param)
+          break;
+
+        if (!cs.is_first)
+          cs.len++;
+        cs.len += _backtrace_value_length (param->value);
+      }
+      break;
+
+    case SCOPE_TARGET:
+      var_key.name[0] = '@';
+      var_key.length = 1;
+      name = hash_find_item (&sl->set->table, &var_key);
+      cs.len += strlen (name->value) + 3;
+
+      cs.is_first = 1;
+      cs.skip = SKIP_AT;
+      hash_map_arg (&sl->set->table, _backtrace_scope_count, &cs);
+      break;
+
+    case SCOPE_OTHER:
+      cs.len += strlen (sl->set->scope_name) + 3;
+
+      cs.is_first = 1;
+      cs.skip = SKIP_NONE;
+      hash_map_arg (&sl->set->table, _backtrace_scope_count, &cs);
+      break;
+    }
+  }
+
+  ps.p = get_buffer (cs.len);
+  sprintf (ps.p, "%s:\n", header);
+  ps.p += strlen (ps.p);
+
+  for (sl = current_variable_set_list; sl->next != NULL; sl = sl->next) {
+    switch (sl->set->scope_type) {
+    case SCOPE_FUNCTION:
+      var_key.name[0] = '0';
+      var_key.length = 1;
+      name = hash_find_item (&sl->set->table, &var_key);
+      sprintf (ps.p, "$(call %s,", name->value);
+      ps.p += strlen (ps.p);
+
+      for (ps.is_first = 1, i = 1;; ps.is_first = 0, i++) {
+        sprintf (var_key.name, "%d", i);
+        var_key.length = strlen (var_key.name);
+        param = hash_find_item (&sl->set->table, &var_key);
+        if (!param)
+          break;
+
+        if (!ps.is_first)
+          *ps.p++ = ',';
+        _backtrace_sprint_value (ps.p, param->value);
+        ps.p += strlen (ps.p);
+      }
+
+      sprintf (ps.p, ")\n");
+      ps.p += 2;
+      break;
+
+    case SCOPE_TARGET:
+      var_key.name[0] = '@';
+      var_key.length = 1;
+      name = hash_find_item (&sl->set->table, &var_key);
+      sprintf (ps.p, "%s: ", name->value);
+      ps.p += strlen (ps.p);
+
+      ps.is_first = 1;
+      ps.skip = SKIP_AT;
+      hash_map_arg (&sl->set->table, _backtrace_scope_print, &ps);
+
+      sprintf (ps.p, "\n");
+      ps.p++;
+      break;
+
+    case SCOPE_OTHER:
+      sprintf (ps.p, "%s{", sl->set->scope_name);
+      ps.p += strlen (ps.p);
+
+      ps.is_first = 1;
+      ps.skip = SKIP_NONE;
+      hash_map_arg (&sl->set->table, _backtrace_scope_print, &ps);
+
+      sprintf (ps.p, "}\n");
+      ps.p += 2;
+      break;
+    }
+  }
+
+  outputs (is_err, fmtbuf.buffer);
+}
+
 /* Print an error message.  */
 
 void
@@ -646,6 +846,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

