blob: 82f0cbc872c1e91cf37e3f73a058af8f8584e19e [file] [log] [blame]
/* GNU m4 -- A simple macro processor
Copyright (C) 1991, 1992, 1993, 1994, 2004, 2006, 2007, 2008 Free
Software Foundation, Inc.
This file is part of GNU M4.
GNU M4 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.
GNU M4 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 "m4.h"
#include <stdarg.h>
#include <sys/stat.h>
/* File for debugging output. */
FILE *debug = NULL;
/* Obstack for trace messages. */
static struct obstack trace;
static void debug_set_file (const call_info *, FILE *);
/*----------------------------------.
| Initialize the debugging module. |
`----------------------------------*/
void
debug_init (void)
{
debug_set_file (NULL, stderr);
obstack_init (&trace);
}
/*-------------------------------------------------------------------.
| Function to decode the debugging flags OPTS of length LEN. If LEN |
| is SIZE_MAX, use strlen (OPTS) instead. Used by main while |
| processing option -d, and by the builtin debugmode. |
`-------------------------------------------------------------------*/
int
debug_decode (const char *opts, size_t len)
{
int level;
if (!opts)
opts = "";
if (len == SIZE_MAX)
len = strlen (opts);
if (!len)
level = DEBUG_TRACE_DEFAULT;
else
{
for (level = 0; len--; opts++)
{
switch (*opts)
{
case 'a':
level |= DEBUG_TRACE_ARGS;
break;
case 'e':
level |= DEBUG_TRACE_EXPANSION;
break;
case 'q':
level |= DEBUG_TRACE_QUOTE;
break;
case 't':
level |= DEBUG_TRACE_ALL;
break;
case 'l':
level |= DEBUG_TRACE_LINE;
break;
case 'f':
level |= DEBUG_TRACE_FILE;
break;
case 'p':
level |= DEBUG_TRACE_PATH;
break;
case 'c':
level |= DEBUG_TRACE_CALL;
break;
case 'i':
level |= DEBUG_TRACE_INPUT;
break;
case 'x':
level |= DEBUG_TRACE_CALLID;
break;
case 'V':
level |= DEBUG_TRACE_VERBOSE;
break;
default:
return -1;
}
}
}
return level;
}
/*-----------------------------------------------------------------.
| Change the debug output stream to FP. If the underlying file is |
| the same as stdout, use stdout instead so that debug messages |
| appear in the correct relative position. Report any failure on |
| behalf of CALLER. |
`-----------------------------------------------------------------*/
static void
debug_set_file (const call_info *caller, FILE *fp)
{
struct stat stdout_stat, debug_stat;
if (debug != NULL && debug != stderr && debug != stdout
&& close_stream (debug) != 0)
m4_error (0, errno, caller, _("error writing to debug stream"));
debug = fp;
if (debug != NULL && debug != stdout)
{
if (fstat (STDOUT_FILENO, &stdout_stat) < 0)
return;
if (fstat (fileno (debug), &debug_stat) < 0)
return;
/* mingw has a bug where fstat on a regular file reports st_ino
of 0. On normal system, st_ino should never be 0. */
if (stdout_stat.st_ino == debug_stat.st_ino
&& stdout_stat.st_dev == debug_stat.st_dev
&& stdout_stat.st_ino != 0)
{
if (debug != stderr && close_stream (debug) != 0)
m4_error (0, errno, caller, _("error writing to debug stream"));
debug = stdout;
}
}
}
/*-----------------------------------------------------------.
| Serialize files. Used before executing a system command. |
`-----------------------------------------------------------*/
void
debug_flush_files (void)
{
fflush (stdout);
fflush (stderr);
if (debug != NULL && debug != stdout && debug != stderr)
fflush (debug);
/* POSIX requires that if m4 doesn't consume all input, but stdin is
opened on a seekable file, that the file pointer be left at the
next character on exit (but places no restrictions on the file
pointer location on a non-seekable file). It also requires that
fflush() followed by fseeko() on an input file set the underlying
file pointer, and gnulib guarantees these semantics. However,
fflush() on a non-seekable file can lose buffered data, which we
might otherwise want to process after syscmd. Hence, we must
check whether stdin is seekable. We must also be tolerant of
operating with stdin closed, so we don't report any failures in
this attempt. The stdio-safer module and friends are essential,
so that if stdin was closed, this lseek is not on some other file
that we have since opened. */
if (lseek (STDIN_FILENO, 0, SEEK_CUR) >= 0
&& fflush (stdin) == 0)
{
fseeko (stdin, 0, SEEK_CUR);
}
}
/*-------------------------------------------------------------------.
| Change the debug output to file NAME. If NAME is NULL, debug |
| output is reverted to stderr, and if empty, debug output is |
| discarded. Return true iff the output stream was changed. Report |
| errors on behalf of CALLER. |
`-------------------------------------------------------------------*/
bool
debug_set_output (const call_info *caller, const char *name)
{
FILE *fp;
if (name == NULL)
debug_set_file (caller, stderr);
else if (*name == '\0')
debug_set_file (caller, NULL);
else
{
fp = fopen (name, "a");
if (fp == NULL)
return false;
if (set_cloexec_flag (fileno (fp), true) != 0)
m4_warn (errno, caller, _("cannot protect debug file across forks"));
debug_set_file (caller, fp);
}
return true;
}
/*-----------------------------------------------------------------------.
| Print the header of a one-line debug message, starting by "m4debug:". |
`-----------------------------------------------------------------------*/
void
debug_message (const char *format, ...)
{
va_list args;
if (debug)
{
xfprintf (debug, "m4debug:");
if (current_line)
{
if (debug_level & DEBUG_TRACE_FILE)
xfprintf (debug, "%s:", current_file);
if (debug_level & DEBUG_TRACE_LINE)
xfprintf (debug, "%d:", current_line);
}
putc (' ', debug);
va_start (args, format);
xvfprintf (debug, format, args);
va_end (args);
putc ('\n', debug);
}
}
/* The rest of this file contains the functions for macro tracing output.
All tracing output for a macro call is collected on an obstack TRACE,
and printed whenever the line is complete. This prevents tracing
output from interfering with other debug messages generated by the
various builtins. */
/*------------------------------------------------------------------.
| Format the standard header attached to all tracing output lines, |
| using the context in INFO as appropriate. Return the offset into |
| the trace obstack where this particular trace begins. |
`------------------------------------------------------------------*/
static unsigned int
trace_header (const call_info *info)
{
int trace_level = info->debug_level;
unsigned int result = obstack_object_size (&trace);
obstack_grow (&trace, "m4trace:", 8);
if (trace_level & DEBUG_TRACE_FILE)
obstack_printf (&trace, "%s:", info->file);
if (trace_level & DEBUG_TRACE_LINE)
obstack_printf (&trace, "%d:", info->line);
obstack_printf (&trace, " -%d- ", expansion_level);
if (trace_level & DEBUG_TRACE_CALLID)
obstack_printf (&trace, "id %d: ", info->call_id);
return result;
}
/*-----------------------------------------------------------------.
| Print current tracing line starting at offset START, as returned |
| from an earlier trace_header(), then clear the obstack. |
`-----------------------------------------------------------------*/
static void
trace_flush (unsigned int start)
{
char *base = (char *) obstack_base (&trace);
size_t len = obstack_object_size (&trace);
if (debug)
{
/* TODO - quote nonprintable characters if debug is tty? */
fwrite (&base[start], 1, len - start, debug);
fputc ('\n', debug);
}
obstack_blank (&trace, start - len);
}
/*-------------------------------------------------------------------.
| Do pre-argument-collection tracing for the macro call described in |
| INFO. Used from expand_macro (). |
`-------------------------------------------------------------------*/
void
trace_prepre (const call_info *info)
{
if (info->trace && (info->debug_level & DEBUG_TRACE_CALL))
{
unsigned int start = trace_header (info);
obstack_grow (&trace, info->name, info->name_len);
obstack_grow (&trace, " ...", 4);
trace_flush (start);
}
}
/*------------------------------------------------------------------.
| Format the parts of a trace line that are known via ARGV before |
| the macro is actually expanded. Used from call_macro (). Return |
| the start of the current trace, in case other traces are printed |
| before this trace completes trace_post. |
`------------------------------------------------------------------*/
unsigned int
trace_pre (macro_arguments *argv)
{
const call_info *info = arg_info (argv);
int trace_level = info->debug_level;
unsigned int start = trace_header (info);
assert (info->trace);
obstack_grow (&trace, ARG (0), ARG_LEN (0));
if (1 < arg_argc (argv) && (trace_level & DEBUG_TRACE_ARGS))
{
size_t len = max_debug_argument_length;
obstack_1grow (&trace, '(');
arg_print (&trace, argv, 1,
(trace_level & DEBUG_TRACE_QUOTE) ? &curr_quote : NULL,
false, NULL, ", ", &len, true);
obstack_1grow (&trace, ')');
}
return start;
}
/*------------------------------------------------------------------.
| If requested by the trace state in INFO, format the final part of |
| a trace line. Then print all collected information from START, |
| returned from a prior trace_pre(). Used from call_macro (). |
`------------------------------------------------------------------*/
void
trace_post (unsigned int start, const call_info *info)
{
assert (info->trace);
if (info->debug_level & DEBUG_TRACE_EXPANSION)
{
obstack_grow (&trace, " -> ", 4);
if (info->debug_level & DEBUG_TRACE_QUOTE)
obstack_grow (&trace, curr_quote.str1, curr_quote.len1);
input_print (&trace);
if (info->debug_level & DEBUG_TRACE_QUOTE)
obstack_grow (&trace, curr_quote.str2, curr_quote.len2);
}
trace_flush (start);
}