gcc 3.3.1, GNU/Linux x86: unreachable code warnings?
Steven Augart
augart@us.ibm.com
Tue Aug 26 01:20:00 GMT 2003
Dear GCC maintainers,
I am reporting what appears to be a bug in gcc 3.3.1.
I compile the appended program with GCC as follows:
nice g++ -O1 -Wunreachable-code preprocessModifiedFiles.C
I get the following warnings during compilation:
preprocessModifiedFiles.C: In function `int main(int, char**)':
preprocessModifiedFiles.C:476: warning: will never be executed
preprocessModifiedFiles.C: In function `int preprocess(const char*, const
char*)':
preprocessModifiedFiles.C:786: warning: will never be executed
preprocessModifiedFiles.C:783: warning: will never be executed
preprocessModifiedFiles.C:759: warning: will never be executed
preprocessModifiedFiles.C:739: warning: will never be executed
preprocessModifiedFiles.C:718: warning: will never be executed
preprocessModifiedFiles.C:733: warning: will never be executed
preprocessModifiedFiles.C:617: warning: will never be executed
preprocessModifiedFiles.C:617: warning: will never be executed
preprocessModifiedFiles.C:615: warning: will never be executed
I believe the "will never be executed" warnings are incorrect, except
for the ones at lines 783 and 786, which I expected.
I have spent about two hours tracing through the code and line 476 of
the source file is definitely executed; as this sample run will show you:
Bash$ ./a.out /tmp no-such-file
a.out: Trouble looking at the file "no-such-file": No such file or directory
a.out: Aborting Execution.
I am running on an IBM Netvista type 6792-MHU computer, under a Red
Hat 7.3 GNU/Linux distribution. Linux kernel 2.40.20, 1536MB of RAM,
1GB of swap space. Pentium 4 (1.80 GHz processor). Please let me
know if I can provide you with additional information.
I compiled GCC myself from the 3.3.1 tarball, using gcc 2.96. The
build and install completed normally. Before I compiled gcc, I
compiled and installed binutils 2.14:
Bash$ gcc --version
gcc (GCC) 3.3.1
[...]
Bash$ as --version
GNU assembler 2.14 20030612
[...]
I have tried to trim down the program text I'm appending, but the
really short versions don't show the warning.
Sincerely Yours,
--Steven Augart
Jikes RVM Open Source Java Compiler Project
http://www.ibm.com/developerworks/oss/jikesrvm/
--------preprocessModifiedFiles.C follows-------------------------
/* (C) Copyright IBM Corp. 2001, 2003
$Id: preprocessModifiedFiles.C,v 1.29 2003/08/26 01:17:05 augart-oss Exp $
Documentation for this program is in the variables short_help_msg and
long_help_msg, immediately below.
Jikes RVM is distribed under the Common Public License (CPL),
which has been approved by the Open Source Initiative
as a fully certified open source license.
Jikes is a trademark of IBM Corp.
*/
static const char short_help_msg[] = ""
"Usage: %s [--help] [--trace]\n"
" [ --[no-]undefined-constants-in-conditions ]\n"
" [ --[no-]only-boolean-constants-in-conditions ]\n"
" [ --disable-modification-exit-status ]\n"
" [ -D<name>[ =1 | =0 | =<string-value> ] ]...\n"
" [ -- ] <output directory> [ <input file> ]...\n";
static const char long_help_msg[] = ""
" Preprocess source files that are new or have changed.\n"
"\n"
" The timestamp of each input file is compared with that\n"
" of the corresponding file in the <output directory>. If the\n"
" output file doesn't exist, or is older than the input file,\n"
" then the input file is copied to the <output directory>, \n"
" with preprocessing.\n"
"\n"
" Invocation parameters:\n"
" - zero or more definitions of preprocessor constants\n"
" of the form \"-D<name>=1\",\n"
" of the equivalent shorthand form \"-D<name>\", \n"
" of the form \"-D<name>=0\",\n"
" and/or of the form \"-D<name>=<string-value>\".\n"
" - name of directory to receive output files\n"
" - names of zero or more input files\n"
" - other flags\n"
"\n"
" Process exit status means:\n"
" 0 - no files changed\n"
" 1 - some files changed\n"
" other - trouble\n"
"\n"
" With --disable-modification-exit-status, the process will\n"
" exit with status 0 even when some files changed. Under\n"
" --disable-modification-exit-status, non-zero exit status\n"
" always means trouble. This is handy inside Makefiles.\n"
"\n"
"\n"
" --trace The preprocessor prints a \n"
" '.' for each file that did not need to be changed and a \n"
" '+' for each file that needed preprocessing."
"\n"
" --verbose, -v The preprocessor prints a message for each file\n"
" examined, and prints a summary at the end \n"
"\n"
" --help, -h Show this long help message and exit with status 0.\n"
"\n"
" --keep-going, -k Keep going in spite of errors; still exit with bad status.\n"
"\n"
" -D<name>=0 is historically a no-op; equivalent to never defining <name>.\n"
" (However, the --no-undefined-constants-in-conditions flag changes that\n"
" behavior, requiring that any <name> in an //-#if <name> directive.\n"
" be defined with -D<name>=0 or -D<name>=1.)\n"
"\n"
" -D<name>=1 and -D<name> are equivalent.\n"
"\n"
" -D<name>=<any-string-value-but-0-or-1> will define a constant that is\n"
" usable in a //-#value directive.\n"
"\n"
" The following preprocessor directives are recognized\n"
" in source files. They must be the first non-whitespace characters\n"
" on a line of input.\n"
"\n"
" //-#if <name>\n"
" Historically, it is not an error for <name> to have never been\n"
" defined; it's equivalent to -D<name>=0. This can be \n"
" experimentally promoted to an error with the flag\n"
" --no-undefined-constants-in-conditions. The historical\n"
" behavior (default) is explicitly requested with\n"
" --undefined-constants-in-conditions\n"
" \n"
" Historically, //-#if only checks whether <name> is defined.\n"
" If you specify --only-boolean-constants-in-conditions, then\n"
" you get stricter behavior where <name> must've been defined with\n"
" -D<name>=1 or -D<name>=0.\n"
"\n"
" \"//-#if\" also supports the constructs '!' (invert the sense of \n"
" the next test), '&&', and '||'. '!' binds more tightly \n"
" than '&&' and '||' do. '&&' and '||' are at the same precedence,\n"
" and are short-circuit evaluated in left-to-right order.\n"
"\n"
" The preprocessor does not support parentheses in //-#if constructs.\n"
" If you don't mix '&&' and '||' in the same line, you'll be OK.\n"
"\n"
" //-#elif <name>\n"
" Takes the same arguments that //-#if does. \n"
"\n"
" //-#else <optional-comment>\n"
"\n"
" //-#endif <optional-comment>\n"
"\n"
" //-#value <preprocessor-symbol>\n"
" <-preprocessor-symbol> is the name of a constant defined on the\n"
" command line with -D; it will be replaced with the defined value.\n"
"\n"
" It is an error for <preprocessor-symbol> not to be defined.\n"
"\n"
" It is an error for <preprocessor-symbol> to have been defined\n"
" with -D<name>=1 or with -D<name>\n"
"\n"
" (This is an odd restriction, but is the way the code was written\n"
" when I found it. You're free to rewrite it if you want it to act\n"
" just like the C preprocessor does.)\n"
"\n"
" There is no equivalent to the C preprocessor's \"#define\" construct;\n"
" all constants are defined on the command line with \"-D\".\n";
static const char authors[] =
" @author Derek Lieber\n"
" @date 13 Oct 1999\n"
" @modified Steven Augart\n"
" @date June, 2003\n";
static const char license[] =
"Copyright ©IBM Corp. 2001, 2003.\n"
"This software is redistributable under the terms of the Common Public License,\n"
"an OSI-certified open-source license.\n";
#define _GNU_SOURCE 1 // so we get vsnprintf() guaranteed on gnu libc.
//#define _XOPEN_SOURCE 500 // the most extended version. GNU explicitly
// tests for this being equal to 500. Fun, eh?
#include <stdio.h>
#include <errno.h>
#include <string.h> /* strcmp */
#include <libgen.h> /* basename */
#include <unistd.h> /* unlink */
#include <stdlib.h> /* exit */
#include <sys/stat.h> /* stat */
#if (defined __linux__)
#include <limits.h> /* xxx_MAX */
#else
#include <sys/limits.h> /* xxx_MAX */
#endif
#include <assert.h> // assert()
#include <ctype.h> // isspace()
#include <stdarg.h> // va_list, for snprintf()
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h> // for sigaction() and signal().
#ifndef __cplusplus
#include <stdbool.h>
#endif
char *Me; // name to appear in error messages
//bool only_boolean_constants_in_conditions = true; /* strict behav. */
bool only_boolean_constants_in_conditions = false; /* historical behaviour */
bool undefined_constants_in_conditions = true; /* Historical behavior */
//bool undefined_constants_in_conditions = false; /* Strict behavior */
bool exit_with_status_1_if_files_modified = true; /* Historical behavior */
bool keep_going = false;
#ifndef DEBUG
#define DEBUG 0
#endif
#define SHOW_PROGRESS(STR) if (trace) { printf(STR); fflush(stdout); }
// Limits.
//
#define MAXLINE 4000 /* longest input line we can handle */
#define MAXNESTING 100 /* maximum #if nesting we can handle */
#define MAXCONSTANTS 100 /* maximum number of -D<name> constants we can handle */
// Preprocessor constants that have been set via "-D<name>".
//
struct def {
const char *Name;
const char *Value; // this is the value for the "value"
// construct. "value" is only meaningful if
// this is non-null..
enum { UNINIT = 0, SET = 1, UNSET = -1} isset; // true or false; this is
// only sensible for the
// -D<Name>=0 and
// -D<Name>=1. Otherwise
// it's UNINIT
} Constant[MAXCONSTANTS];
// This kludge gets around the fact that an enum tag is scoped in its defining
// context in C++, whereas in standard C, enum tags are global identifiers.
#ifdef __cplusplus
#define membof(o) o
#else
#define membof(o)
#endif
int Constants; // number thereof
// Source file currently being processed.
//
char package[MAXLINE]; // package name if it exists
const char *SourceName; // file name
int SourceLineNo; // current line number therein
int Nesting; // number of unclosed #if's
/* Structure for unmatched conditionals. This is a simple stack. */
/* At nesting level 1, the item is stored in array element 0, and so on.
* Ugly, no? */
struct nest_st {
int lineNo; // line number of currently active #if, #elif, or #else at each level
char lineTxt[MAXLINE]; // A copy of the current line, but
// with the line ending chopped off
// (for error messages)
bool trueSeen; // has any block evaluated true at the current nesting level?
bool val; // has the current block at current nesting level evaluated true
} unmatched[MAXNESTING];
bool PassLines; // if true, pass lines through. false --> don't
char *PutIntoPackage = NULL; // points to memory that's part of argv.
// Forward references, and prototypes to shut up GCC's paranoid warning
// settings.
int preprocess(const char *srcFile, const char *destinationFile);
void reviseState(void);
#if DEBUG
void printState(FILE *fout, char *constant, char *line);
#endif
bool eval(char *p, int &trouble);
const char *evalReplace(char *cursor, int &trouble);
char *getToken(char **c, int &trouble);
bool getBoolean(char **c);
// Types of tokens returned by scan().
//
enum scan_token {
TT_TEXT = 0, // nothing to replace.
TT_IF = 1, // arg.If
TT_ELIF = 2, // arg.If
TT_ELSE = 3, // no arg
TT_ENDIF = 4, // no arg
TT_REPLACE = 5, // arg.Replace
TT_UNRECOGNIZED = 6,
};
union scan_arg {
bool If;
const char *Replace;
};
enum scan_token scan(const char *srcFile, char *line, union scan_arg *argp,
int &trouble);
#define UNUSED_DECL_ARG __attribute__((__unused__))
#define UNUSED_DEF_ARG __attribute__((__unused__))
// The __signal__ attribute is only relevant on GCC on the AVR processor.
// We don't (yet) work on the AVR, so this code will probably never be
// executed.
#ifdef __avr__
#define SIGNAL_ATTRIBUTE __attribute__((__signal__))
#else
#define SIGNAL_ATTRIBUTE
#endif
/* snprintf(), but with our own built-in error checks. */
void xsnprintf(char *buf, size_t bufsize, const char *format, ...)
__attribute__((__format__(__printf__, 3, 4)));
void xsystem(const char *command);
void inputErr(const char msg[], ...)
// __attribute__((noreturn))
__attribute__((__format__(__printf__, 1, 2)));
static void set_up_trouble_handlers();
bool strneql(const char *s, const char *t, size_t n) ;
bool streql(const char *s, const char *t);
static void shorthelp(FILE *out);
/** Delete this file on trouble. This is the interface to
* delete_on_trouble(). */
const char *DeleteOnTrouble = NULL;
static void delete_on_trouble(void) SIGNAL_ATTRIBUTE;
enum Trouble { TROUBLE = -1, OK = 0 };
int
main(int argc, char **argv)
{
Me = basename(*argv++); --argc;
set_up_trouble_handlers();
// gather arguments
//
bool trace = false;
bool verbose = false;
for (; **argv == '-'; ++argv, --argc) {
char *arg = *argv;
if (streql(arg, "--")) {
++argv, --argc;
break;
}
if (strneql(arg, "--", 2)) {
++arg; /* treat double-dash and single-dash the same way. */
}
if (streql(arg, "-help") || streql(arg, "-h")) {
printf(short_help_msg, Me);
fputs(long_help_msg, stdout);
exit(0);
}
if (streql(arg, "-keep-going") || streql(arg, "-k")) {
keep_going = true;
continue;
}
if (streql(arg, "-disable-modification-exit-status")) {
exit_with_status_1_if_files_modified = false;
continue;
}
if (streql(arg, "-trace")) {
trace = true;
continue;
}
if (streql(arg, "-verbose") || streql(arg, "-v")) {
verbose = true;
continue;
}
if (streql(arg, "-undefined-constants-in-conditions")) {
undefined_constants_in_conditions = true;
continue;
}
if (streql(arg, "-no-undefined-constants-in-conditions")) {
undefined_constants_in_conditions = false;
continue;
}
if (streql(arg, "-only-boolean-constants-in-conditions")) {
only_boolean_constants_in_conditions = true;
continue;
}
if (streql(arg, "-no-only-boolean-constants-in-conditions")) {
only_boolean_constants_in_conditions = false;
continue;
}
if (strneql(arg, "-D", 2)) {
struct def *dp = Constant + Constants;
arg += 2;
dp->Name = arg;
for ( ; *arg && *arg != '='; ++arg)
;
char *val;
if (*arg == '=') {
*arg = '\0'; // null-terminate the name.
val = arg + 1;
} else {
assert(*arg == '\0');
val = NULL; // Special sentinel value.
}
if (++Constants >= MAXCONSTANTS) {
fprintf(stderr, "\
%s: Too many (%d) -D constants; recompile with a larger\n"
"value of MAXCONSTANTS.\n",
Me, Constants);
exit(3);
}
// We used to ignore "-D<name>=0"; now we explicitly define it as
// UNSET.
// accept "-D<name>" as "-D<name>=1" == SET
// accept "-D<name>=<str>" for any other string
if (*(dp->Name) == '\0') {
fprintf(stderr, "%s: The -D<name>[=<value>] flag needs\n"
"at least a <name>! None found in definition # %d.\n",
Me , Constants);
shorthelp(stderr);
exit(2);
}
// -D<name> with no =<value> is a special case.
// Treat it as if -D<name>=1.
// -D<name>=1 is also a special case.
if (!val || streql(val, "1")) {
assert(dp->Value == NULL);
dp->isset = membof(dp->) SET;
continue;
}
// -D<name>=0. Special case. Used to be equivalent to never
// setting.
if (streql(val, "0")) {
// should always be initialized to NULL, since Constants is
// global (BSS) space.
assert(dp->Value == NULL);
dp->isset = membof(dp->) UNSET;
continue; // ignore it (!)
}
// Must be -D<name>=<value>
dp->Value = val;
// should always be initialized to UNINIT, since Constants is
// global (BSS) space.
assert(dp->isset == membof(dp->) UNINIT);
continue;
}
/* We'll consider multiple -package declarations benign, and let
* the last one win.. */
if (streql(arg, "-package")) {
--argc;
if (! *++argv ) {
fprintf(stderr, "%s: The -package flag requires an argument\n", Me);
shorthelp(stderr);
exit(2);
}
PutIntoPackage = *argv;
}
if (strneql(arg, "-package=", 9)) {
PutIntoPackage = arg + 9;
continue;
}
fprintf(stderr, "%s: unrecognized option: %s\n", Me, arg);
shorthelp(stderr);
exit(2);
}
char *outputDirectory = *argv++; --argc;
// check timestamps and preprocess new/changed files
//
int examined = 0;
int preprocessed = 0;
int failed = 0;
if (argc == 0 && (trace || verbose)) {
fprintf(stderr, "%s: I won't preprocess any files, since you didn't\n"
" specify any on the command line.", Me);
}
while (argc > 0) {
char *source = *argv++; --argc;
char destination[PATH_MAX + 1];
xsnprintf(destination, sizeof destination, "%s/%s", outputDirectory, basename(source));
struct stat info;
time_t sourceTime;
time_t destinationTime;
if (stat(source, &info) < 0) {
fprintf(stderr, "%s: Trouble looking at the file \"%s\": %s\n",
Me, source, strerror(errno));
if (keep_going) {
++failed;
continue;
}
fprintf(stderr, "%s: Aborting Execution.\n", Me);
exit(2);
}
sourceTime = info.st_mtime;
if (stat(destination, &info) < 0) {
destinationTime = 0;
} else {
destinationTime = info.st_mtime;
}
if (sourceTime > destinationTime) {
if (verbose) {
if (destinationTime == 0) {
fprintf(stdout, "%s: \"%s\" has never been processed: processing\n", Me, basename(source));
} else {
fprintf(stdout, "%s: \"%s\" changed since last time: reprocessing\n", Me, basename(source));
}
}
// file is new or has changed
// // make (previously preprocessed) output file writable
// if (destinationTime != 0)
// chmod(destination, S_IREAD | S_IWRITE);
if (preprocess(source, DeleteOnTrouble = destination) < 0) {
if (keep_going) {
++failed;
delete_on_trouble();
continue;
}
fprintf(stderr, "%s: Aborting Execution.\n", Me);
exit(2); // treat as fatal error
}
DeleteOnTrouble = NULL;
// Move file to the right subdirectory if it is part of a package
//
if (*package) {
// Should do error-checking of package
char command[PATH_MAX + 100];
char finalDir[PATH_MAX + 1];
char finalDstFile[PATH_MAX + 1];
// Convert "." in package to "/"
char *cur = package;
while ((cur = strchr(cur, '.')) != NULL) {
*cur = '/';
}
xsnprintf(finalDir, sizeof finalDir, "%s/%s", outputDirectory, package);
xsnprintf(finalDstFile, sizeof finalDstFile, "%s/%s", finalDir, basename(source));
xsnprintf(command, sizeof command, "mkdir -p %s", finalDir);
//fprintf(stderr, "%s\n", command);
xsystem(command);
xsnprintf(command, sizeof command, "mv -f %s %s", destination, finalDstFile);
//fprintf(stderr, "%s\n", command);
xsystem(command);
}
// // make output file non-writable to discourage editing that will get clobbered by future preprocessing runs
// chmod(destination, S_IREAD);
++preprocessed;
SHOW_PROGRESS("+");
}
else
SHOW_PROGRESS(".");
++examined;
}
if (verbose)
fprintf(stdout, "\n%s: %d of %d files required preprocessing\n", Me, preprocessed, examined);
// SHOW_PROGRESS("\n");
if (failed > 0) {
if (verbose)
fprintf(stdout, "\n%s: %d additional files had trouble and weren't preprocessed\n", Me, failed);
exit(2);
}
if (exit_with_status_1_if_files_modified && preprocessed)
exit(1);
exit(0);
}
// Preprocess a file.
// Takes: name of file to be read
// name of file to be written
// Returns: 0 --> success
// -1 --> trouble
// Whether trouble leads to immediate aborting by the caller depends upon the
// value of keep_going.
int
preprocess(const char *srcFile, const char *dstFile)
{
int trouble = OK; // have we encountered any trouble yet?
*package = 0;
FILE *fin = fopen(srcFile, "r");
if (!fin) {
fprintf(stderr, "%s: can't find `%s'\n", Me, srcFile);
return TROUBLE;
}
FILE *fout = fopen(dstFile, "w");
if (!fout) {
fprintf(stderr, "%s: can't create `%s':", Me, dstFile);
perror("");
(void) fclose(fin); // in case keep_going is set.
return TROUBLE;
}
#if DEBUG
for (int i = 0; i < Constants; ++i) {
struct def *dp = Constant + i;
fprintf(fout, "// [%s=%s]\n", dp->Name,
dp->Value ? :
( dp->isset == membof(dp->) SET ? "1 (*SET*)" : "0 (*UNSET*)"));
}
#endif
SourceName = srcFile;
SourceLineNo = 0;
Nesting = 0;
reviseState();
if (PutIntoPackage != NULL)
fprintf(fout, "\npackage %s;\n\n", PutIntoPackage);
for (;;) {
char line[MAXLINE];
size_t linelen;
if (ferror(fout)) {
fprintf(stderr, "%s: Trouble while writing to output file: ", Me);
perror(dstFile);
/* Let's not keep going on this file in the face of I/O
* trouble. */
(void) fclose(fin);
(void) fclose(fout);
return TROUBLE;
}
if (!fgets(line, sizeof line, fin)) {
if (feof(fin)) {
if (fclose(fin)) {
fprintf(stderr,
"%s Trouble while closing an input file: ", Me);
perror(srcFile);
if (!keep_going)
return TROUBLE;
trouble = TROUBLE;
};
if (fclose(fout)) {
fprintf(stderr,
"%s: Trouble while closing an output file: ");
perror(dstFile);
if (!keep_going)
return TROUBLE;
trouble = TROUBLE;
}
if (Nesting) {
do {
struct nest_st *u = &unmatched[--Nesting];
fprintf(stderr, "%s: %s:%d: Never found a matching #endif for this line: %s\n", Me, SourceName, u->lineNo, u->lineTxt);
} while (Nesting > 0);
if (!keep_going)
return TROUBLE;
trouble = TROUBLE;
}
// Done preprocessing the file!
return trouble;
}
if (ferror(fin)) {
fprintf(stderr, "%s: Trouble while reading the file \"%s\": ",
Me, srcFile);
perror("");
/* Let's not keep going on this file in the face of I/O
* trouble. */
(void) fclose(fin);
(void) fclose(fout);
return TROUBLE;
}
fprintf(stderr, "%s: Internal error: fgets() returned NULL, but\n"
" neither feof() nor ferror() are true!\n"
" This should never happen.\n"
"%s: Aborting execution.\n", Me, Me);
exit(13);
}
linelen = strlen(line);
assert(linelen > 0); // otherwise we'd have gotten feof().
if (line[ linelen - 1 ] != '\n') {
inputErr("Line too long (over %lu characters).\n",
(unsigned long) linelen);
exit(13);
}
++SourceLineNo;
enum scan_token token_type;
union scan_arg scanned;
switch (token_type = scan(srcFile, line, &scanned, trouble)) {
case TT_TEXT:
#if DEBUG
printState(fout, "TEXT ", line);
#endif
// Note: We could do error checking on each write, but we'll check
// at fclose() time instead, since ferror() is persistent until
// cleared.
fputs(PassLines ? line : "\n", fout);
continue;
case TT_REPLACE:
#if DEBUG
printState(fout, "REPLACE ", line);
#endif
fputs(PassLines ? scanned.Replace : "\n", fout);
continue;
case TT_IF:
{
/* temp. pointer to current unmatched struct */
register struct nest_st *u = &unmatched[Nesting++];
u->trueSeen = u->val = scanned.If;
u->lineNo = SourceLineNo;
strcpy(u->lineTxt, line);
char *nlp = strchr(u->lineTxt, '\n');
assert(nlp); // must be newline terminated. That's how
// fgets() works, and we tested above.
*nlp = '\0';
reviseState();
#if DEBUG
printState(fout, "IF ", line);
#endif
fputs("\n", fout);
continue;
}
case TT_ELIF:
{
if (Nesting == 0) {
inputErr("#elif with no corresponding #if");
continue;
}
register struct nest_st *u = &unmatched[Nesting - 1];
if (u->trueSeen)
u->val = false;
else
u->trueSeen = u->val = scanned.If;
u->lineNo = SourceLineNo;
reviseState();
#if DEBUG
printState(fout, "ELIF ", line);
#endif
fputs("\n", fout);
continue;
}
case TT_ELSE:
{
if (Nesting == 0) {
inputErr("#else with no corresponding #if");
continue;
}
register struct nest_st *u = &unmatched[Nesting - 1];
if (u->trueSeen)
u->val = false;
else
u->trueSeen = u->val = true;
u->lineNo = SourceLineNo;
reviseState();
#if DEBUG
printState(fout, "ELSE ", line);
#endif
fputs("\n", fout);
continue;
}
case TT_ENDIF:
if (Nesting == 0) {
inputErr("#endif with no corresponding #if: %s", line);
continue;
}
--Nesting;
reviseState();
#if DEBUG
printState(fout, "ENDIF", line);
#endif
fputs("\n", fout);
continue;
case TT_UNRECOGNIZED:
inputErr("unrecognized preprocessor directive: %s", line);
continue;
default:
fprintf(stderr, "%s: %s:%d: Internal error: scan() should never return token type %d\n", Me, SourceName, SourceLineNo, token_type);
exit(13);
}
assert(false);
/* NOTREACHED */
}
assert(false);
/* NOTREACHED */
}
// Compute new preprocessor state after scanning a constant.
// Uses: nesting[].val
// Nesting
// Sets: PassLines
//
void reviseState(void)
{
PassLines = true;
for (int i = 0; i < Nesting; ++i)
PassLines = PassLines && unmatched[i].val;
}
#if DEBUG
// Print preprocessor state (for debugging).
// Taken: output file
// current constant
// current input line
// Returned: nothing
//
void
printState(FILE *fout, char *constant, char *line)
{
fprintf(fout, "[stack=");
int i;
for (i = 0; i < Nesting; ++i)
fprintf(fout, "%s ", unmatched[i].val ? "true" : "false");
for (; i < 5; ++i)
fprintf(fout, "..");
fprintf(fout, "%s] %s %s", PassLines ? "pass" : "hide", constant, line);
}
#endif
// Scan for a preprocessor directive. Also handles "package" declarations.
//
// Taken: line to be scanned
// argp: place to put value of #if or #elif directive, if found
// or place to put replacement text
// Returned: TT_TEXT --> found no directive
// TT_IF --> found '//-#if <name>' directive
// TT_ELIF --> found `//-#elif <name>' directive
// TT_ELSE --> found `//-#else' directive
// TT_ENDIF --> found `//-#endif' directive
// TT_REPLACE --> found `//-#value <name>' directive
// TT_UNRECOGNIZED --> found unrecognized directive
// In some cases, value is modified.
enum scan_token
scan(const char *srcFile, char *line, union scan_arg *argp, int &trouble)
{
// skip whitespace
//
char *p;
for (p = line; *p && isspace(*p); ++p)
;
// look for "package [c.]*;"
//
if (strneql(p, "package ", 8)) {
if (PutIntoPackage)
fprintf(stderr, "WARNING: package declaration co-existing with specified package via -package");
if (*package)
fprintf(stderr, "WARNING: multiple package declaration in file %s", srcFile);
p += 8;
char *tmp = package;
while (*p != ';') {
if (*p == '\n') fprintf(stderr, "%s: Ill-formed package declaration: %s", srcFile, line);
*(tmp++) = *(p++);
}
*tmp = 0;
return TT_TEXT;
}
// look for "//-#"
//
if (*p++ != '/') return TT_TEXT;
if (*p++ != '/') return TT_TEXT;
if (*p++ != '-') return TT_TEXT;
if (*p++ != '#') return TT_TEXT;
// look for "value "
//
if (strneql(p, "value", 5) && isspace(p[5])) {
p +=6;
while (isspace(*p))
++p;
argp->Replace = evalReplace(p, trouble);
return TT_REPLACE;
}
// look for "if "
//
if (strneql(p, "if", 2) && isspace(p[2])) {
p +=3;
while (isspace(*p))
++p;
argp->If = eval(p, trouble);
return TT_IF;
}
// look for "elif"
//
if (strneql(p, "elif", 4) && isspace(p[4])) {
p += 5;
while (isspace(*p))
++p;
argp->If = eval(p, trouble );
return TT_ELIF;
}
// look for "else"
//
if (strneql(p, "else", 4)) {
return TT_ELSE;
}
// look for "endif"
//
if (strneql(p, "endif", 5)) {
return TT_ENDIF;
}
return TT_UNRECOGNIZED;
}
// Evaluate <name> appearing in an `if' or `elif' directive.
// Taken: `//-#if <condition>'
// ^cursor
// or: `//-#elif <condition>'
// ^cursor
// where condition is name <&& name> <|| name>
// (!name toggles the sense)
// Returns the name of the token, but NOT null terminated
// Upon return, *c points to the cursor, the character immediately after
// the end of the name
// It will return the leading ! if one exists.
//
// The only characters forbidden to a token are '&', '|', and whitespace.
char *
getToken(char **cursorp, int __attribute__((unused)) &trouble)
{
char *start, *cursor;
cursor = *cursorp;
while (isspace(*cursor))
++cursor;
start = cursor;
while (*cursor && !isspace(*cursor) && *cursor != '&' && *cursor != '|')
++cursor;
*cursorp = cursor;
return start;
}
// Returned: true --> on top of a boolean operator ("||"
// or "&&")
// false --> not on top of one.
// Trims leading whitespace, leaves the cursor before the bool. operator.
bool getBoolean(char **cursorp)
{
while (isspace(**cursorp))
++*cursorp;
return ((*cursorp)[0] == '|' && (*cursorp)[1] == '|')
|| ((*cursorp)[0] == '&' && (*cursorp)[1] == '&');
}
/* Returns a pointer to the replacement token for a //-#value directive. */
const char *
evalReplace(char *cursor, int &trouble)
{
char *name = getToken( &cursor, trouble );
assert( ( cursor == name ) ? ( *name == '\0' ) : *name);
size_t len = cursor - name;
if (len == 0) {
inputErr("The //-#value <name> preprocessor construct needs a <name>");
trouble = TROUBLE;
return "BOGUS #value";
}
for (int i = 0; i < Constants; ++i) {
struct def *dp = Constant + i;
if (strlen(dp->Name) == len && strneql(name, dp->Name, len)) {
if (! dp->Value) {
inputErr(
"//-#value used on non-value (true/false) constant '%s'",
dp->Name);
trouble = TROUBLE;
return dp->Name; // Error return
}
return dp->Value;
}
}
inputErr("//-#value used on undefined constant '%*.*s'", (int) len, (int) len, name);
trouble = TROUBLE;
return name;
}
bool
eval(char *cursor, int &trouble)
{
int match; // -1 if not found, 1 if defined true (1),
// 0 if defined 0 (false).
for (;;) {
match = -1;
char *name = getToken( &cursor, trouble );
int toggle = 0;
if ( name[0] == '!' ) {
toggle = 1;
name++;
}
assert(cursor >= name);
size_t len = cursor - name;
if (len == 0) {
inputErr("missing <name> in preprocessor condition");
trouble = TROUBLE;
return false; // did not match.
}
for (int i = 0; i < Constants; ++i) {
struct def *dp = Constant + i;
if (strlen(dp->Name) == len && memcmp(dp->Name, name, len) == 0) {
/* Matched the token. Now evaluate. */
if (dp->Value) {
if (only_boolean_constants_in_conditions) {
inputErr(
"The constant '%*.*s' is a Value constant;\n"
" //-#if and //-#elif require a"
" Boolean (1 or 0) constant.", (int) len, (int) len, dp->Name);
trouble = TROUBLE;
return false; // So we will assume false.
}
match = 1;
} else {
assert(!dp->Value);
assert(dp->isset != membof(dp->) UNINIT);
match = (dp->isset == membof(dp->) SET);
}
break;
}
}
if (match < 0) {
if (! undefined_constants_in_conditions) {
inputErr("Undefined constant named \"%*.*s\""
" in preprocessor condition", (int) len, (int) len, name);
trouble = TROUBLE;
}
match = 0;
}
if ( toggle )
match = !match;
if ( !getBoolean( &cursor ) )
break;
if ( cursor[0] == '|' ) {
if ( match ) // skip further syntax checking; TODO: Check
// the rest of the syntax.
return true;
} else {
assert(*cursor == '&');
if ( !match ) // skip further checking.
return false;
}
cursor += 2; // skip && or ||
}
while (*cursor && isspace(*cursor))
++cursor;
if (*cursor) {
inputErr("Garbage characters (\"%s\") are at the"
" end of a preprocessor condition.", cursor);
trouble = TROUBLE;
}
return match;
}
/* snprintf(), but with our own built-in error checks. */
void
xsnprintf(char *buf, size_t bufsize, const char *format, ...)
{
va_list ap;
va_start(ap, format);
int n = vsnprintf(buf, bufsize, format, ap);
va_end(ap);
// Handle both the old and new GNU C library return values.
if (n < 0 || n + 1 > (int) bufsize) {
fprintf(stderr, "%s: xsnprintf(): Ran out of space in a"
" fixed-size buffer while formatting a string"
" starting with: \"%s\"\n", Me, buf);
fprintf(stderr, "%s: Aborting Execution.\n", Me);
exit(4);
}
/* All is well. */
}
void
xsystem(const char *command)
{
int ret;
ret = system(command);
if (ret == 0)
return;
if (ret < 0) {
fputs(Me, stderr);
perror(": Trouble while trying to use system()");
fprintf(stderr, "%s: Aborting Execution.\n", Me);
exit(5);
}
fprintf(stderr, "%s: Trouble while running system(\"%s\").\n", Me, command);
if (WIFEXITED(ret)) {
if (WEXITSTATUS(ret) == 127) {
fputs(" The shell probably) could not be executed.\n", stderr);
} else {
fprintf(stderr, " The command exited with status %d\n",
WEXITSTATUS(ret));
}
} else if (WIFSIGNALED(ret)) {
fprintf(stderr, " The command died because it got hit with signal #%d\n", WTERMSIG(ret));
} else {
fprintf(stderr, " !!! The system() function failed for some reason that we do not understand; return status was %d. You should never see this message. Aborting execution.\n", ret);
exit(13);
}
fputs(" Aborting execution.\n", stderr);
exit(5);
}
void
shorthelp(FILE *out)
{
fprintf(out, short_help_msg, Me);
fprintf(out, "%s: Use --help for more information.\n", Me);
}
static void
delete_on_trouble()
{
if (DeleteOnTrouble) {
// We're already in trouble, so might as well delete, no?
int err = unlink(DeleteOnTrouble);
if (err && errno != ENOENT) {
fprintf(stderr, "%s: More Trouble -- unable to clean up by deleting ", Me);
perror(DeleteOnTrouble);
}
DeleteOnTrouble = NULL;
}
}
/* Signal handler for fatal signals. */
static void
cleanup_and_die(int signum)
{
fprintf(stderr, "%s: Dying due to signal # %d; cleaning up.\n", Me, signum);
delete_on_trouble();
signal(signum, SIG_DFL);
raise(signum);
}
/* These are like strcmp() and strncmp(), but with a more intuitive
interface (at least, more intuitive for those of us who did not grow
up using the FORTRAN Arithmetic IF!) */
bool
strneql(const char *s, const char *t, size_t n)
{
return strncmp(s, t, n) == 0;
}
bool
streql(const char *s, const char *t)
{
return strcmp(s, t) == 0;
}
void
inputErr(const char msg[], ...)
{
va_list ap;
va_start(ap, msg);
fprintf(stderr, "%s: %s:%d: ", Me, SourceName, SourceLineNo);
vfprintf(stderr, msg, ap);
va_end(ap); /* silly to clean up; we're going to die
* anyway. */
putc('\n', stderr);
if (keep_going) {
delete_on_trouble();
return;
}
fprintf(stderr, "%s: Aborting Execution.\n", Me);
exit(2);
}
#if 0 /* This appears to work fine, but some say
that sigaction() is more predictable. */
static void
xsignal_oldsignal(int signum, void (*handler)(int))
{
void (*ret)(int) = signal(signum, handler);
if (ret == SIG_ERR) {
fprintf(stderr,
"%s: Trouble trying to set up a handler for signal %d: ",
Me, signum);
perror(NULL);
fprintf(stderr, "%s: ...going on as best we can\n");
} else if (ret == SIG_IGN) {
/* Some shells may turn off handling of certain signals. We shan't
interfere. */
// ignore return value; nothing much to do anyway.
signal(signum, SIG_IGN);
}
}
#endif
static void
xsignal_sigaction(int signum, void (*handler)(int))
{
struct sigaction act;
struct sigaction oldact;
memset(&act, '\0', sizeof act);
act.sa_handler = handler;
act.sa_flags |= SA_RESTART;
int r = sigaction(signum, &act, &oldact);
if (r) {
fprintf(stderr,
"%s: Trouble trying to set up a handler for signal %d: ",
Me, signum);
perror((char *) NULL);
fprintf(stderr, "%s: ...going on as best we can\n", Me);
return;
}
if (oldact.sa_handler == SIG_IGN) {
/* Don't interfere with shells that turn off the handling of certain
signals; reset it, instead. */
r = sigaction(signum, &oldact, (struct sigaction *) NULL);
if (r) {
fprintf(stderr, "%s: Trouble resetting signal %d's handler back to SIG_IGN: ", Me, signum);
perror((char *) NULL);
fprintf(stderr, "%s: ...going on as best we can.", Me);
}
}
/* Ok; all is done! We're either set up or we aren't. */
}
static void (*xsignal)(int signum, void (*handler)(int)) = xsignal_sigaction;
static void
set_up_trouble_handlers(void)
{
if (atexit(&delete_on_trouble)) {
fprintf(stderr,"%s: ", Me);
perror("Unable to register delete_on_trouble() via on_exit. This should never happen");
// exit(2);
}
xsignal(SIGINT, cleanup_and_die);
xsignal(SIGHUP, cleanup_and_die);
xsignal(SIGTERM, cleanup_and_die);
xsignal(SIGQUIT, cleanup_and_die);
xsignal(SIGPIPE, cleanup_and_die);
xsignal(SIGXFSZ, cleanup_and_die);
xsignal(SIGXCPU, cleanup_and_die);
xsignal(SIGFPE, cleanup_and_die);
xsignal(SIGILL, cleanup_and_die);
xsignal(SIGSEGV, cleanup_and_die);
xsignal(SIGBUS, cleanup_and_die);
xsignal(SIGABRT, cleanup_and_die);
#ifdef SIGEMT
xsignal(SIGEMT, cleanup_and_die);
#endif
xsignal(SIGSYS, cleanup_and_die);
}
More information about the Gcc-bugs
mailing list