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
debug_init(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
debug_decode(const char * opts)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
debug_set_file(FILE * fp)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
debug_flush_files(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
debug_set_output(const char * name)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
debug_message_prefix(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
trace_format(const char * fmt,...)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
trace_header(int id)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
trace_flush(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
trace_prepre(const char * name,int id)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
trace_pre(const char * name,int id,int argc,token_data ** argv)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
trace_post(const char * name,int id,int argc,const char * expanded)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 }
438