(root)/
m4-1.4.19/
src/
debug.c
       1  /* GNU m4 -- A simple macro processor
       2  
       3     Copyright (C) 1991-1994, 2004, 2006-2007, 2009-2014, 2016-2017,
       4     2020-2021 Free Software Foundation, Inc.
       5  
       6     This file is part of GNU M4.
       7  
       8     GNU M4 is free software: you can redistribute it and/or modify
       9     it under the terms of the GNU General Public License as published by
      10     the Free Software Foundation, either version 3 of the License, or
      11     (at your option) any later version.
      12  
      13     GNU M4 is distributed in the hope that it will be useful,
      14     but WITHOUT ANY WARRANTY; without even the implied warranty of
      15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      16     GNU General Public License for more details.
      17  
      18     You should have received a copy of the GNU General Public License
      19     along with this program.  If not, see <https://www.gnu.org/licenses/>.
      20  */
      21  
      22  #include "m4.h"
      23  
      24  #include <stdarg.h>
      25  #include <sys/stat.h>
      26  
      27  /* File for debugging output.  */
      28  FILE *debug = NULL;
      29  
      30  /* Obstack for trace messages.  */
      31  static struct obstack trace;
      32  
      33  static void debug_set_file (FILE *);
      34  
      35  /*----------------------------------.
      36  | Initialise the debugging module.  |
      37  `----------------------------------*/
      38  
      39  void
      40  debug_init (void)
      41  {
      42    debug_set_file (stderr);
      43    obstack_init (&trace);
      44  }
      45  
      46  /*-----------------------------------------------------------------.
      47  | Function to decode the debugging flags OPTS.  Used by main while |
      48  | processing option -d, and by the builtin debugmode ().           |
      49  `-----------------------------------------------------------------*/
      50  
      51  int
      52  debug_decode (const char *opts)
      53  {
      54    int level;
      55  
      56    if (opts == NULL || *opts == '\0')
      57      level = DEBUG_TRACE_DEFAULT;
      58    else
      59      {
      60        for (level = 0; *opts; opts++)
      61          {
      62            switch (*opts)
      63              {
      64              case 'a':
      65                level |= DEBUG_TRACE_ARGS;
      66                break;
      67  
      68              case 'e':
      69                level |= DEBUG_TRACE_EXPANSION;
      70                break;
      71  
      72              case 'q':
      73                level |= DEBUG_TRACE_QUOTE;
      74                break;
      75  
      76              case 't':
      77                level |= DEBUG_TRACE_ALL;
      78                break;
      79  
      80              case 'l':
      81                level |= DEBUG_TRACE_LINE;
      82                break;
      83  
      84              case 'f':
      85                level |= DEBUG_TRACE_FILE;
      86                break;
      87  
      88              case 'p':
      89                level |= DEBUG_TRACE_PATH;
      90                break;
      91  
      92              case 'c':
      93                level |= DEBUG_TRACE_CALL;
      94                break;
      95  
      96              case 'i':
      97                level |= DEBUG_TRACE_INPUT;
      98                break;
      99  
     100              case 'x':
     101                level |= DEBUG_TRACE_CALLID;
     102                break;
     103  
     104              case 'V':
     105                level |= DEBUG_TRACE_VERBOSE;
     106                break;
     107  
     108              default:
     109                return -1;
     110              }
     111          }
     112      }
     113  
     114    /* This is to avoid screwing up the trace output due to changes in the
     115       debug_level.  */
     116  
     117    obstack_free (&trace, obstack_finish (&trace));
     118  
     119    return level;
     120  }
     121  
     122  /*-----------------------------------------------------------------.
     123  | Change the debug output stream to FP.  If the underlying file is |
     124  | the same as stdout, use stdout instead so that debug messages    |
     125  | appear in the correct relative position.                         |
     126  `-----------------------------------------------------------------*/
     127  
     128  static void
     129  debug_set_file (FILE *fp)
     130  {
     131    struct stat stdout_stat, debug_stat;
     132  
     133    if (debug != NULL && debug != stderr && debug != stdout
     134        && close_stream (debug) != 0)
     135      {
     136        M4ERROR ((warning_status, errno, _("error writing to debug stream")));
     137        retcode = EXIT_FAILURE;
     138      }
     139    debug = fp;
     140  
     141    if (debug != NULL && debug != stdout)
     142      {
     143        if (fstat (STDOUT_FILENO, &stdout_stat) < 0)
     144          return;
     145        if (fstat (fileno (debug), &debug_stat) < 0)
     146          return;
     147  
     148        /* mingw has a bug where fstat on a regular file reports st_ino
     149           of 0.  On normal system, st_ino should never be 0.  */
     150        if (stdout_stat.st_ino == debug_stat.st_ino
     151            && stdout_stat.st_dev == debug_stat.st_dev
     152            && stdout_stat.st_ino != 0)
     153          {
     154            if (debug != stderr && close_stream (debug) != 0)
     155              {
     156                M4ERROR ((warning_status, errno,
     157                          _("error writing to debug stream")));
     158                retcode = EXIT_FAILURE;
     159              }
     160            debug = stdout;
     161          }
     162      }
     163  }
     164  
     165  /*-----------------------------------------------------------.
     166  | Serialize files.  Used before executing a system command.  |
     167  `-----------------------------------------------------------*/
     168  
     169  void
     170  debug_flush_files (void)
     171  {
     172    fflush (stdout);
     173    fflush (stderr);
     174    if (debug != NULL && debug != stdout && debug != stderr)
     175      fflush (debug);
     176    /* POSIX requires that if m4 doesn't consume all input, but stdin is
     177       opened on a seekable file, that the file pointer be left at the
     178       next character on exit (but places no restrictions on the file
     179       pointer location on a non-seekable file).  It also requires that
     180       fflush() followed by fseeko() on an input file set the underlying
     181       file pointer, and gnulib guarantees these semantics.  However,
     182       fflush() on a non-seekable file can lose buffered data, which we
     183       might otherwise want to process after syscmd.  Hence, we must
     184       check whether stdin is seekable.  We must also be tolerant of
     185       operating with stdin closed, so we don't report any failures in
     186       this attempt.  The stdio-safer module and friends are essential,
     187       so that if stdin was closed, this lseek is not on some other file
     188       that we have since opened.  */
     189    if (lseek (STDIN_FILENO, 0, SEEK_CUR) >= 0
     190        && fflush (stdin) == 0)
     191      {
     192        fseeko (stdin, 0, SEEK_CUR);
     193      }
     194  }
     195  
     196  /*--------------------------------------------------------------.
     197  | Change the debug output to file NAME.  If NAME is NULL, debug |
     198  | output is reverted to stderr, and if empty, debug output is   |
     199  | discarded.  Return true iff the output stream was changed.    |
     200  `--------------------------------------------------------------*/
     201  
     202  bool
     203  debug_set_output (const char *name)
     204  {
     205    FILE *fp;
     206  
     207    if (name == NULL)
     208      debug_set_file (stderr);
     209    else if (*name == '\0')
     210      debug_set_file (NULL);
     211    else
     212      {
     213        fp = fopen (name, "ae");
     214        if (fp == NULL)
     215          return false;
     216        debug_set_file (fp);
     217      }
     218    return true;
     219  }
     220  
     221  /*--------------------------------------------------------------.
     222  | Print the header of a one-line debug message, starting by "m4 |
     223  | debug".                                                       |
     224  `--------------------------------------------------------------*/
     225  
     226  void
     227  debug_message_prefix (void)
     228  {
     229    xfprintf (debug, "m4debug:");
     230    if (current_line)
     231    {
     232      if (debug_level & DEBUG_TRACE_FILE)
     233        xfprintf (debug, "%s:", current_file);
     234      if (debug_level & DEBUG_TRACE_LINE)
     235        xfprintf (debug, "%d:", current_line);
     236    }
     237    putc (' ', debug);
     238  }
     239  
     240  /* The rest of this file contains the functions for macro tracing output.
     241     All tracing output for a macro call is collected on an obstack TRACE,
     242     and printed whenever the line is complete.  This prevents tracing
     243     output from interfering with other debug messages generated by the
     244     various builtins.  */
     245  
     246  /*------------------------------------------------------------------.
     247  | Tracing output is formatted here, by a simplified                 |
     248  | printf-to-obstack function trace_format ().  Understands only %S, |
     249  | %s, %d, %l (optional left quote) and %r (optional right quote).   |
     250  `------------------------------------------------------------------*/
     251  
     252  static void
     253  trace_format (const char *fmt, ...)
     254  {
     255    va_list args;
     256    char ch;
     257  
     258    int d;
     259    const char *s;
     260    int slen;
     261    int maxlen;
     262  
     263    va_start (args, fmt);
     264  
     265    while (true)
     266      {
     267        while ((ch = *fmt++) != '\0' && ch != '%')
     268          obstack_1grow (&trace, ch);
     269  
     270        if (ch == '\0')
     271          break;
     272  
     273        maxlen = 0;
     274        switch (*fmt++)
     275          {
     276          case 'S':
     277            maxlen = max_debug_argument_length;
     278            FALLTHROUGH;
     279          case 's':
     280            s = va_arg (args, const char *);
     281            break;
     282  
     283          case 'l':
     284            s = (debug_level & DEBUG_TRACE_QUOTE) ? lquote.string : "";
     285            break;
     286  
     287          case 'r':
     288            s = (debug_level & DEBUG_TRACE_QUOTE) ? rquote.string : "";
     289            break;
     290  
     291          case 'd':
     292            d = va_arg (args, int);
     293            s = ntoa (d, 10);
     294            break;
     295  
     296          default:
     297            s = "";
     298            break;
     299          }
     300  
     301        slen = strlen (s);
     302        if (maxlen == 0 || maxlen > slen)
     303          obstack_grow (&trace, s, slen);
     304        else
     305          {
     306            obstack_grow (&trace, s, maxlen);
     307            obstack_grow (&trace, "...", 3);
     308          }
     309      }
     310  
     311    va_end (args);
     312  }
     313  
     314  /*------------------------------------------------------------------.
     315  | Format the standard header attached to all tracing output lines.  |
     316  `------------------------------------------------------------------*/
     317  
     318  static void
     319  trace_header (int id)
     320  {
     321    trace_format ("m4trace:");
     322    if (current_line)
     323      {
     324        if (debug_level & DEBUG_TRACE_FILE)
     325          trace_format ("%s:", current_file);
     326        if (debug_level & DEBUG_TRACE_LINE)
     327          trace_format ("%d:", current_line);
     328      }
     329    trace_format (" -%d- ", expansion_level);
     330    if (debug_level & DEBUG_TRACE_CALLID)
     331      trace_format ("id %d: ", id);
     332  }
     333  
     334  /*----------------------------------------------------.
     335  | Print current tracing line, and clear the obstack.  |
     336  `----------------------------------------------------*/
     337  
     338  static void
     339  trace_flush (void)
     340  {
     341    char *line;
     342  
     343    obstack_1grow (&trace, '\0');
     344    line = (char *) obstack_finish (&trace);
     345    DEBUG_PRINT1 ("%s\n", line);
     346    obstack_free (&trace, line);
     347  }
     348  
     349  /*-------------------------------------------------------------.
     350  | Do pre-argument-collction tracing for macro NAME.  Used from |
     351  | expand_macro ().                                             |
     352  `-------------------------------------------------------------*/
     353  
     354  void
     355  trace_prepre (const char *name, int id)
     356  {
     357    trace_header (id);
     358    trace_format ("%s ...", name);
     359    trace_flush ();
     360  }
     361  
     362  /*--------------------------------------------------------------.
     363  | Format the parts of a trace line, that can be made before the |
     364  | macro is actually expanded.  Used from expand_macro ().       |
     365  `--------------------------------------------------------------*/
     366  
     367  void
     368  trace_pre (const char *name, int id, int argc, token_data **argv)
     369  {
     370    int i;
     371    const builtin *bp;
     372  
     373    trace_header (id);
     374    trace_format ("%s", name);
     375  
     376    if (argc > 1 && (debug_level & DEBUG_TRACE_ARGS))
     377      {
     378        trace_format ("(");
     379  
     380        for (i = 1; i < argc; i++)
     381          {
     382            if (i != 1)
     383              trace_format (", ");
     384  
     385            switch (TOKEN_DATA_TYPE (argv[i]))
     386              {
     387              case TOKEN_TEXT:
     388                trace_format ("%l%S%r", TOKEN_DATA_TEXT (argv[i]));
     389                break;
     390  
     391              case TOKEN_FUNC:
     392                bp = find_builtin_by_addr (TOKEN_DATA_FUNC (argv[i]));
     393                if (bp == NULL)
     394                  {
     395                    M4ERROR ((warning_status, 0, "\
     396  INTERNAL ERROR: builtin not found in builtin table! (trace_pre ())"));
     397                    abort ();
     398                  }
     399                trace_format ("<%s>", bp->name);
     400                break;
     401  
     402              case TOKEN_VOID:
     403              default:
     404                M4ERROR ((warning_status, 0,
     405                          "INTERNAL ERROR: bad token data type (trace_pre ())"));
     406                abort ();
     407              }
     408  
     409          }
     410        trace_format (")");
     411      }
     412  
     413    if (debug_level & DEBUG_TRACE_CALL)
     414      {
     415        trace_format (" -> ???");
     416        trace_flush ();
     417      }
     418  }
     419  
     420  /*-------------------------------------------------------------------.
     421  | Format the final part of a trace line and print it all.  Used from |
     422  | expand_macro ().                                                   |
     423  `-------------------------------------------------------------------*/
     424  
     425  void
     426  trace_post (const char *name, int id, int argc, const char *expanded)
     427  {
     428    if (debug_level & DEBUG_TRACE_CALL)
     429      {
     430        trace_header (id);
     431        trace_format ("%s%s", name, (argc > 1) ? "(...)" : "");
     432      }
     433  
     434    if (expanded && (debug_level & DEBUG_TRACE_EXPANSION))
     435      trace_format (" -> %l%S%r", expanded);
     436    trace_flush ();
     437  }