1/* 2 * Copyright (c) 2002 - 2013 Tony Finch <dot@dotat.at> 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 * SUCH DAMAGE. 24 */ 25 26/* 27 * unifdef - remove ifdef'ed lines 28 * 29 * This code was derived from software contributed to Berkeley by Dave Yost. 30 * It was rewritten to support ANSI C by Tony Finch. The original version 31 * of unifdef carried the 4-clause BSD copyright licence. None of its code 32 * remains in this version (though some of the names remain) so it now 33 * carries a more liberal licence. 34 * 35 * Wishlist: 36 * provide an option which will append the name of the 37 * appropriate symbol after #else's and #endif's 38 * provide an option which will check symbols after 39 * #else's and #endif's to see that they match their 40 * corresponding #ifdef or #ifndef 41 * 42 * These require better buffer handling, which would also make 43 * it possible to handle all "dodgy" directives correctly. 44 */ 45 46#include "unifdef.h" 47 48static const char copyright[] = 49 "@(#) $Version: unifdef-2.7 $\n" 50 "@(#) $FreeBSD$\n" 51 "@(#) $Author: Tony Finch (dot@dotat.at) $\n" 52 "@(#) $URL: http://dotat.at/prog/unifdef $\n" 53; 54 55/* types of input lines: */ 56typedef enum { 57 LT_TRUEI, /* a true #if with ignore flag */ 58 LT_FALSEI, /* a false #if with ignore flag */ 59 LT_IF, /* an unknown #if */ 60 LT_TRUE, /* a true #if */ 61 LT_FALSE, /* a false #if */ 62 LT_ELIF, /* an unknown #elif */ 63 LT_ELTRUE, /* a true #elif */ 64 LT_ELFALSE, /* a false #elif */ 65 LT_ELSE, /* #else */ 66 LT_ENDIF, /* #endif */ 67 LT_DODGY, /* flag: directive is not on one line */ 68 LT_DODGY_LAST = LT_DODGY + LT_ENDIF, 69 LT_PLAIN, /* ordinary line */ 70 LT_EOF, /* end of file */ 71 LT_ERROR, /* unevaluable #if */ 72 LT_COUNT 73} Linetype; 74 75static char const * const linetype_name[] = { 76 "TRUEI", "FALSEI", "IF", "TRUE", "FALSE", 77 "ELIF", "ELTRUE", "ELFALSE", "ELSE", "ENDIF", 78 "DODGY TRUEI", "DODGY FALSEI", 79 "DODGY IF", "DODGY TRUE", "DODGY FALSE", 80 "DODGY ELIF", "DODGY ELTRUE", "DODGY ELFALSE", 81 "DODGY ELSE", "DODGY ENDIF", 82 "PLAIN", "EOF", "ERROR" 83}; 84 85#define linetype_if2elif(lt) ((Linetype)(lt - LT_IF + LT_ELIF)) 86#define linetype_2dodgy(lt) ((Linetype)(lt + LT_DODGY)) 87 88/* state of #if processing */ 89typedef enum { 90 IS_OUTSIDE, 91 IS_FALSE_PREFIX, /* false #if followed by false #elifs */ 92 IS_TRUE_PREFIX, /* first non-false #(el)if is true */ 93 IS_PASS_MIDDLE, /* first non-false #(el)if is unknown */ 94 IS_FALSE_MIDDLE, /* a false #elif after a pass state */ 95 IS_TRUE_MIDDLE, /* a true #elif after a pass state */ 96 IS_PASS_ELSE, /* an else after a pass state */ 97 IS_FALSE_ELSE, /* an else after a true state */ 98 IS_TRUE_ELSE, /* an else after only false states */ 99 IS_FALSE_TRAILER, /* #elifs after a true are false */ 100 IS_COUNT 101} Ifstate; 102 103static char const * const ifstate_name[] = { 104 "OUTSIDE", "FALSE_PREFIX", "TRUE_PREFIX", 105 "PASS_MIDDLE", "FALSE_MIDDLE", "TRUE_MIDDLE", 106 "PASS_ELSE", "FALSE_ELSE", "TRUE_ELSE", 107 "FALSE_TRAILER" 108}; 109 110/* state of comment parser */ 111typedef enum { 112 NO_COMMENT = false, /* outside a comment */ 113 C_COMMENT, /* in a comment like this one */ 114 CXX_COMMENT, /* between // and end of line */ 115 STARTING_COMMENT, /* just after slash-backslash-newline */ 116 FINISHING_COMMENT, /* star-backslash-newline in a C comment */ 117 CHAR_LITERAL, /* inside '' */ 118 STRING_LITERAL /* inside "" */ 119} Comment_state; 120 121static char const * const comment_name[] = { 122 "NO", "C", "CXX", "STARTING", "FINISHING", "CHAR", "STRING" 123}; 124 125/* state of preprocessor line parser */ 126typedef enum { 127 LS_START, /* only space and comments on this line */ 128 LS_HASH, /* only space, comments, and a hash */ 129 LS_DIRTY /* this line can't be a preprocessor line */ 130} Line_state; 131 132static char const * const linestate_name[] = { 133 "START", "HASH", "DIRTY" 134}; 135 136/* 137 * Minimum translation limits from ISO/IEC 9899:1999 5.2.4.1 138 */ 139#define MAXDEPTH 64 /* maximum #if nesting */ 140#define MAXLINE 4096 /* maximum length of line */ 141#define MAXSYMS 4096 /* maximum number of symbols */ 142 143/* 144 * Sometimes when editing a keyword the replacement text is longer, so 145 * we leave some space at the end of the tline buffer to accommodate this. 146 */ 147#define EDITSLOP 10 148 149/* 150 * Globals. 151 */ 152 153static bool compblank; /* -B: compress blank lines */ 154static bool lnblank; /* -b: blank deleted lines */ 155static bool complement; /* -c: do the complement */ 156static bool debugging; /* -d: debugging reports */ 157static bool inplace; /* -m: modify in place */ 158static bool iocccok; /* -e: fewer IOCCC errors */ 159static bool strictlogic; /* -K: keep ambiguous #ifs */ 160static bool killconsts; /* -k: eval constant #ifs */ 161static bool lnnum; /* -n: add #line directives */ 162static bool symlist; /* -s: output symbol list */ 163static bool symdepth; /* -S: output symbol depth */ 164static bool text; /* -t: this is a text file */ 165 166static const char *symname[MAXSYMS]; /* symbol name */ 167static const char *value[MAXSYMS]; /* -Dsym=value */ 168static bool ignore[MAXSYMS]; /* -iDsym or -iUsym */ 169static int nsyms; /* number of symbols */ 170 171static FILE *input; /* input file pointer */ 172static const char *filename; /* input file name */ 173static int linenum; /* current line number */ 174static const char *linefile; /* file name for #line */ 175static FILE *output; /* output file pointer */ 176static const char *ofilename; /* output file name */ 177static const char *backext; /* backup extension */ 178static char *tempname; /* avoid splatting input */ 179 180static char tline[MAXLINE+EDITSLOP];/* input buffer plus space */ 181static char *keyword; /* used for editing #elif's */ 182 183static const char *newline; /* input file format */ 184static const char newline_unix[] = "\n"; 185static const char newline_crlf[] = "\r\n"; 186 187static Comment_state incomment; /* comment parser state */ 188static Line_state linestate; /* #if line parser state */ 189static Ifstate ifstate[MAXDEPTH]; /* #if processor state */ 190static bool ignoring[MAXDEPTH]; /* ignore comments state */ 191static int stifline[MAXDEPTH]; /* start of current #if */ 192static int depth; /* current #if nesting */ 193static int delcount; /* count of deleted lines */ 194static unsigned blankcount; /* count of blank lines */ 195static unsigned blankmax; /* maximum recent blankcount */ 196static bool constexpr; /* constant #if expression */ 197static bool zerosyms; /* to format symdepth output */ 198static bool firstsym; /* ditto */ 199 200static int exitmode; /* exit status mode */ 201static int exitstat; /* program exit status */ 202 203static void addsym(bool, bool, char *); 204static char *astrcat(const char *, const char *); 205static void cleantemp(void); 206static void closeout(void); 207static void debug(const char *, ...); 208static void done(void); 209static void error(const char *); 210static int findsym(const char *); 211static void flushline(bool); 212static void hashline(void); 213static void help(void); 214static Linetype ifeval(const char **); 215static void ignoreoff(void); 216static void ignoreon(void); 217static void keywordedit(const char *); 218static void nest(void); 219static Linetype parseline(void); 220static void process(void); 221static void processinout(const char *, const char *); 222static const char *skipargs(const char *); 223static const char *skipcomment(const char *); 224static const char *skipsym(const char *); 225static void state(Ifstate); 226static int strlcmp(const char *, const char *, size_t); 227static void unnest(void); 228static void usage(void); 229static void version(void); 230 231#define endsym(c) (!isalnum((unsigned char)c) && c != '_') 232 233/* 234 * The main program. 235 */ 236int 237main(int argc, char *argv[]) 238{ 239 int opt; 240 241 while ((opt = getopt(argc, argv, "i:D:U:I:M:o:x:bBcdehKklmnsStV")) != -1) 242 switch (opt) { 243 case 'i': /* treat stuff controlled by these symbols as text */ 244 /* 245 * For strict backwards-compatibility the U or D 246 * should be immediately after the -i but it doesn't 247 * matter much if we relax that requirement. 248 */ 249 opt = *optarg++; 250 if (opt == 'D') 251 addsym(true, true, optarg); 252 else if (opt == 'U') 253 addsym(true, false, optarg); 254 else 255 usage(); 256 break; 257 case 'D': /* define a symbol */ 258 addsym(false, true, optarg); 259 break; 260 case 'U': /* undef a symbol */ 261 addsym(false, false, optarg); 262 break; 263 case 'I': /* no-op for compatibility with cpp */ 264 break; 265 case 'b': /* blank deleted lines instead of omitting them */ 266 case 'l': /* backwards compatibility */ 267 lnblank = true; 268 break; 269 case 'B': /* compress blank lines around removed section */ 270 compblank = true; 271 break; 272 case 'c': /* treat -D as -U and vice versa */ 273 complement = true; 274 break; 275 case 'd': 276 debugging = true; 277 break; 278 case 'e': /* fewer errors from dodgy lines */ 279 iocccok = true; 280 break; 281 case 'h': 282 help(); 283 break; 284 case 'K': /* keep ambiguous #ifs */ 285 strictlogic = true; 286 break; 287 case 'k': /* process constant #ifs */ 288 killconsts = true; 289 break; 290 case 'm': /* modify in place */ 291 inplace = true; 292 break; 293 case 'M': /* modify in place and keep backup */ 294 inplace = true; 295 backext = optarg; 296 break; 297 case 'n': /* add #line directive after deleted lines */ 298 lnnum = true; 299 break; 300 case 'o': /* output to a file */ 301 ofilename = optarg; 302 break; 303 case 's': /* only output list of symbols that control #ifs */ 304 symlist = true; 305 break; 306 case 'S': /* list symbols with their nesting depth */ 307 symlist = symdepth = true; 308 break; 309 case 't': /* don't parse C comments */ 310 text = true; 311 break; 312 case 'V': 313 version(); 314 break; 315 case 'x': 316 exitmode = atoi(optarg); 317 if(exitmode < 0 || exitmode > 2) 318 usage(); 319 break; 320 default: 321 usage(); 322 } 323 argc -= optind; 324 argv += optind; 325 if (compblank && lnblank) 326 errx(2, "-B and -b are mutually exclusive"); 327 if (symlist && (ofilename != NULL || inplace || argc > 1)) 328 errx(2, "-s only works with one input file"); 329 if (argc > 1 && ofilename != NULL) 330 errx(2, "-o cannot be used with multiple input files"); 331 if (argc > 1 && !inplace) 332 errx(2, "multiple input files require -m or -M"); 333 if (argc == 0) 334 argc = 1; 335 if (argc == 1 && !inplace && ofilename == NULL) 336 ofilename = "-"; 337 338 atexit(cleantemp); 339 if (ofilename != NULL) 340 processinout(*argv, ofilename); 341 else while (argc-- > 0) { 342 processinout(*argv, *argv); 343 argv++; 344 } 345 switch(exitmode) { 346 case(0): exit(exitstat); 347 case(1): exit(!exitstat); 348 case(2): exit(0); 349 default: abort(); /* bug */ 350 } 351} 352 353/* 354 * File logistics. 355 */ 356static void 357processinout(const char *ifn, const char *ofn) 358{ 359 struct stat st; 360 361 if (ifn == NULL || strcmp(ifn, "-") == 0) { 362 filename = "[stdin]"; 363 linefile = NULL; 364 input = fbinmode(stdin); 365 } else { 366 filename = ifn; 367 linefile = ifn; 368 input = fopen(ifn, "rb"); 369 if (input == NULL) 370 err(2, "can't open %s", ifn); 371 } 372 if (strcmp(ofn, "-") == 0) { 373 output = fbinmode(stdout); 374 process(); 375 return; 376 } 377 if (stat(ofn, &st) < 0) { 378 output = fopen(ofn, "wb"); 379 if (output == NULL) 380 err(2, "can't create %s", ofn); 381 process(); 382 return; 383 } 384 385 tempname = astrcat(ofn, ".XXXXXX"); 386 output = mktempmode(tempname, st.st_mode); 387 if (output == NULL) 388 err(2, "can't create %s", tempname); 389 390 process(); 391 392 if (backext != NULL) { 393 char *backname = astrcat(ofn, backext); 394 if (rename(ofn, backname) < 0) 395 err(2, "can't rename \"%s\" to \"%s%s\"", ofn, ofn, backext); 396 free(backname); 397 } 398 if (rename(tempname, ofn) < 0) 399 err(2, "can't rename \"%s\" to \"%s\"", tempname, ofn); 400 free(tempname); 401 tempname = NULL; 402} 403 404/* 405 * For cleaning up if there is an error. 406 */ 407static void 408cleantemp(void) 409{ 410 if (tempname != NULL) 411 remove(tempname); 412} 413 414/* 415 * Self-identification functions. 416 */ 417 418static void 419version(void) 420{ 421 const char *c = copyright; 422 for (;;) { 423 while (*++c != '$') 424 if (*c == '\0') 425 exit(0); 426 while (*++c != '$') 427 putc(*c, stderr); 428 putc('\n', stderr); 429 } 430} 431 432static void 433synopsis(FILE *fp) 434{ 435 fprintf(fp, 436 "usage: unifdef [-bBcdehKkmnsStV] [-x{012}] [-Mext] [-opath] \\\n" 437 " [-[i]Dsym[=val]] [-[i]Usym] ... [file] ...\n"); 438} 439 440static void 441usage(void) 442{ 443 synopsis(stderr); 444 exit(2); 445} 446 447static void 448help(void) 449{ 450 synopsis(stdout); 451 printf( 452 " -Dsym=val define preprocessor symbol with given value\n" 453 " -Dsym define preprocessor symbol with value 1\n" 454 " -Usym preprocessor symbol is undefined\n" 455 " -iDsym=val \\ ignore C strings and comments\n" 456 " -iDsym ) in sections controlled by these\n" 457 " -iUsym / preprocessor symbols\n" 458 " -b blank lines instead of deleting them\n" 459 " -B compress blank lines around deleted section\n" 460 " -c complement (invert) keep vs. delete\n" 461 " -d debugging mode\n" 462 " -e ignore multiline preprocessor directives\n" 463 " -h print help\n" 464 " -Ipath extra include file path (ignored)\n" 465 " -K disable && and || short-circuiting\n" 466 " -k process constant #if expressions\n" 467 " -Mext modify in place and keep backups\n" 468 " -m modify input files in place\n" 469 " -n add #line directives to output\n" 470 " -opath output file name\n" 471 " -S list #if control symbols with nesting\n" 472 " -s list #if control symbols\n" 473 " -t ignore C strings and comments\n" 474 " -V print version\n" 475 " -x{012} exit status mode\n" 476 ); 477 exit(0); 478} 479 480/* 481 * A state transition function alters the global #if processing state 482 * in a particular way. The table below is indexed by the current 483 * processing state and the type of the current line. 484 * 485 * Nesting is handled by keeping a stack of states; some transition 486 * functions increase or decrease the depth. They also maintain the 487 * ignore state on a stack. In some complicated cases they have to 488 * alter the preprocessor directive, as follows. 489 * 490 * When we have processed a group that starts off with a known-false 491 * #if/#elif sequence (which has therefore been deleted) followed by a 492 * #elif that we don't understand and therefore must keep, we edit the 493 * latter into a #if to keep the nesting correct. We use memcpy() to 494 * overwrite the 4 byte token "elif" with "if " without a '\0' byte. 495 * 496 * When we find a true #elif in a group, the following block will 497 * always be kept and the rest of the sequence after the next #elif or 498 * #else will be discarded. We edit the #elif into a #else and the 499 * following directive to #endif since this has the desired behaviour. 500 * 501 * "Dodgy" directives are split across multiple lines, the most common 502 * example being a multi-line comment hanging off the right of the 503 * directive. We can handle them correctly only if there is no change 504 * from printing to dropping (or vice versa) caused by that directive. 505 * If the directive is the first of a group we have a choice between 506 * failing with an error, or passing it through unchanged instead of 507 * evaluating it. The latter is not the default to avoid questions from 508 * users about unifdef unexpectedly leaving behind preprocessor directives. 509 */ 510typedef void state_fn(void); 511 512/* report an error */ 513static void Eelif (void) { error("Inappropriate #elif"); } 514static void Eelse (void) { error("Inappropriate #else"); } 515static void Eendif(void) { error("Inappropriate #endif"); } 516static void Eeof (void) { error("Premature EOF"); } 517static void Eioccc(void) { error("Obfuscated preprocessor control line"); } 518/* plain line handling */ 519static void print (void) { flushline(true); } 520static void drop (void) { flushline(false); } 521/* output lacks group's start line */ 522static void Strue (void) { drop(); ignoreoff(); state(IS_TRUE_PREFIX); } 523static void Sfalse(void) { drop(); ignoreoff(); state(IS_FALSE_PREFIX); } 524static void Selse (void) { drop(); state(IS_TRUE_ELSE); } 525/* print/pass this block */ 526static void Pelif (void) { print(); ignoreoff(); state(IS_PASS_MIDDLE); } 527static void Pelse (void) { print(); state(IS_PASS_ELSE); } 528static void Pendif(void) { print(); unnest(); } 529/* discard this block */ 530static void Dfalse(void) { drop(); ignoreoff(); state(IS_FALSE_TRAILER); } 531static void Delif (void) { drop(); ignoreoff(); state(IS_FALSE_MIDDLE); } 532static void Delse (void) { drop(); state(IS_FALSE_ELSE); } 533static void Dendif(void) { drop(); unnest(); } 534/* first line of group */ 535static void Fdrop (void) { nest(); Dfalse(); } 536static void Fpass (void) { nest(); Pelif(); } 537static void Ftrue (void) { nest(); Strue(); } 538static void Ffalse(void) { nest(); Sfalse(); } 539/* variable pedantry for obfuscated lines */ 540static void Oiffy (void) { if (!iocccok) Eioccc(); Fpass(); ignoreon(); } 541static void Oif (void) { if (!iocccok) Eioccc(); Fpass(); } 542static void Oelif (void) { if (!iocccok) Eioccc(); Pelif(); } 543/* ignore comments in this block */ 544static void Idrop (void) { Fdrop(); ignoreon(); } 545static void Itrue (void) { Ftrue(); ignoreon(); } 546static void Ifalse(void) { Ffalse(); ignoreon(); } 547/* modify this line */ 548static void Mpass (void) { memcpy(keyword, "if ", 4); Pelif(); } 549static void Mtrue (void) { keywordedit("else"); state(IS_TRUE_MIDDLE); } 550static void Melif (void) { keywordedit("endif"); state(IS_FALSE_TRAILER); } 551static void Melse (void) { keywordedit("endif"); state(IS_FALSE_ELSE); } 552 553static state_fn * const trans_table[IS_COUNT][LT_COUNT] = { 554/* IS_OUTSIDE */ 555{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Eendif, 556 Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Eendif, 557 print, done, abort }, 558/* IS_FALSE_PREFIX */ 559{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Mpass, Strue, Sfalse,Selse, Dendif, 560 Idrop, Idrop, Fdrop, Fdrop, Fdrop, Mpass, Eioccc,Eioccc,Eioccc,Eioccc, 561 drop, Eeof, abort }, 562/* IS_TRUE_PREFIX */ 563{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Dfalse,Dfalse,Dfalse,Delse, Dendif, 564 Oiffy, Oiffy, Fpass, Oif, Oif, Eioccc,Eioccc,Eioccc,Eioccc,Eioccc, 565 print, Eeof, abort }, 566/* IS_PASS_MIDDLE */ 567{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Pelif, Mtrue, Delif, Pelse, Pendif, 568 Oiffy, Oiffy, Fpass, Oif, Oif, Pelif, Oelif, Oelif, Pelse, Pendif, 569 print, Eeof, abort }, 570/* IS_FALSE_MIDDLE */ 571{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Pelif, Mtrue, Delif, Pelse, Pendif, 572 Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eioccc,Eioccc,Eioccc,Eioccc,Eioccc, 573 drop, Eeof, abort }, 574/* IS_TRUE_MIDDLE */ 575{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Melif, Melif, Melif, Melse, Pendif, 576 Oiffy, Oiffy, Fpass, Oif, Oif, Eioccc,Eioccc,Eioccc,Eioccc,Pendif, 577 print, Eeof, abort }, 578/* IS_PASS_ELSE */ 579{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Pendif, 580 Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Pendif, 581 print, Eeof, abort }, 582/* IS_FALSE_ELSE */ 583{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eelif, Eelif, Eelif, Eelse, Dendif, 584 Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eelif, Eelif, Eelif, Eelse, Eioccc, 585 drop, Eeof, abort }, 586/* IS_TRUE_ELSE */ 587{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Dendif, 588 Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Eioccc, 589 print, Eeof, abort }, 590/* IS_FALSE_TRAILER */ 591{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Dfalse,Dfalse,Dfalse,Delse, Dendif, 592 Idrop, Idrop, Fdrop, Fdrop, Fdrop, Dfalse,Dfalse,Dfalse,Delse, Eioccc, 593 drop, Eeof, abort } 594/*TRUEI FALSEI IF TRUE FALSE ELIF ELTRUE ELFALSE ELSE ENDIF 595 TRUEI FALSEI IF TRUE FALSE ELIF ELTRUE ELFALSE ELSE ENDIF (DODGY) 596 PLAIN EOF ERROR */ 597}; 598 599/* 600 * State machine utility functions 601 */ 602static void 603ignoreoff(void) 604{ 605 if (depth == 0) 606 abort(); /* bug */ 607 ignoring[depth] = ignoring[depth-1]; 608} 609static void 610ignoreon(void) 611{ 612 ignoring[depth] = true; 613} 614static void 615keywordedit(const char *replacement) 616{ 617 snprintf(keyword, tline + sizeof(tline) - keyword, 618 "%s%s", replacement, newline); 619 print(); 620} 621static void 622nest(void) 623{ 624 if (depth > MAXDEPTH-1) 625 abort(); /* bug */ 626 if (depth == MAXDEPTH-1) 627 error("Too many levels of nesting"); 628 depth += 1; 629 stifline[depth] = linenum; 630} 631static void 632unnest(void) 633{ 634 if (depth == 0) 635 abort(); /* bug */ 636 depth -= 1; 637} 638static void 639state(Ifstate is) 640{ 641 ifstate[depth] = is; 642} 643 644/* 645 * The last state transition function. When this is called, 646 * lineval == LT_EOF, so the process() loop will terminate. 647 */ 648static void 649done(void) 650{ 651 if (incomment) 652 error("EOF in comment"); 653 closeout(); 654} 655 656/* 657 * Write a line to the output or not, according to command line options. 658 * If writing fails, closeout() will print the error and exit. 659 */ 660static void 661flushline(bool keep) 662{ 663 if (symlist) 664 return; 665 if (keep ^ complement) { 666 bool blankline = tline[strspn(tline, " \t\r\n")] == '\0'; 667 if (blankline && compblank && blankcount != blankmax) { 668 delcount += 1; 669 blankcount += 1; 670 } else { 671 if (lnnum && delcount > 0) 672 hashline(); 673 if (fputs(tline, output) == EOF) 674 closeout(); 675 delcount = 0; 676 blankmax = blankcount = blankline ? blankcount + 1 : 0; 677 } 678 } else { 679 if (lnblank && fputs(newline, output) == EOF) 680 closeout(); 681 exitstat = 1; 682 delcount += 1; 683 blankcount = 0; 684 } 685 if (debugging && fflush(output) == EOF) 686 closeout(); 687} 688 689/* 690 * Format of #line directives depends on whether we know the input filename. 691 */ 692static void 693hashline(void) 694{ 695 int e; 696 697 if (linefile == NULL) 698 e = fprintf(output, "#line %d%s", linenum, newline); 699 else 700 e = fprintf(output, "#line %d \"%s\"%s", 701 linenum, linefile, newline); 702 if (e < 0) 703 closeout(); 704} 705 706/* 707 * Flush the output and handle errors. 708 */ 709static void 710closeout(void) 711{ 712 /* Tidy up after findsym(). */ 713 if (symdepth && !zerosyms) 714 printf("\n"); 715 if (ferror(output) || fclose(output) == EOF) 716 err(2, "%s: can't write to output", filename); 717} 718 719/* 720 * The driver for the state machine. 721 */ 722static void 723process(void) 724{ 725 Linetype lineval = LT_PLAIN; 726 /* When compressing blank lines, act as if the file 727 is preceded by a large number of blank lines. */ 728 blankmax = blankcount = 1000; 729 zerosyms = true; 730 while (lineval != LT_EOF) { 731 lineval = parseline(); 732 trans_table[ifstate[depth]][lineval](); 733 debug("process line %d %s -> %s depth %d", 734 linenum, linetype_name[lineval], 735 ifstate_name[ifstate[depth]], depth); 736 } 737} 738 739/* 740 * Parse a line and determine its type. We keep the preprocessor line 741 * parser state between calls in the global variable linestate, with 742 * help from skipcomment(). 743 */ 744static Linetype 745parseline(void) 746{ 747 const char *cp; 748 int cursym; 749 int kwlen; 750 Linetype retval; 751 Comment_state wascomment; 752 753 linenum++; 754 if (fgets(tline, MAXLINE, input) == NULL) { 755 if (ferror(input)) 756 err(2, "can't read %s", filename); 757 else 758 return (LT_EOF); 759 } 760 if (newline == NULL) { 761 if (strrchr(tline, '\n') == strrchr(tline, '\r') + 1) 762 newline = newline_crlf; 763 else 764 newline = newline_unix; 765 } 766 retval = LT_PLAIN; 767 wascomment = incomment; 768 cp = skipcomment(tline); 769 if (linestate == LS_START) { 770 if (*cp == '#') { 771 linestate = LS_HASH; 772 firstsym = true; 773 cp = skipcomment(cp + 1); 774 } else if (*cp != '\0') 775 linestate = LS_DIRTY; 776 } 777 if (!incomment && linestate == LS_HASH) { 778 keyword = tline + (cp - tline); 779 cp = skipsym(cp); 780 kwlen = cp - keyword; 781 /* no way can we deal with a continuation inside a keyword */ 782 if (strncmp(cp, "\\\r\n", 3) == 0 || 783 strncmp(cp, "\\\n", 2) == 0) 784 Eioccc(); 785 if (strlcmp("ifdef", keyword, kwlen) == 0 || 786 strlcmp("ifndef", keyword, kwlen) == 0) { 787 cp = skipcomment(cp); 788 if ((cursym = findsym(cp)) < 0) 789 retval = LT_IF; 790 else { 791 retval = (keyword[2] == 'n') 792 ? LT_FALSE : LT_TRUE; 793 if (value[cursym] == NULL) 794 retval = (retval == LT_TRUE) 795 ? LT_FALSE : LT_TRUE; 796 if (ignore[cursym]) 797 retval = (retval == LT_TRUE) 798 ? LT_TRUEI : LT_FALSEI; 799 } 800 cp = skipsym(cp); 801 } else if (strlcmp("if", keyword, kwlen) == 0) 802 retval = ifeval(&cp); 803 else if (strlcmp("elif", keyword, kwlen) == 0) 804 retval = linetype_if2elif(ifeval(&cp)); 805 else if (strlcmp("else", keyword, kwlen) == 0) 806 retval = LT_ELSE; 807 else if (strlcmp("endif", keyword, kwlen) == 0) 808 retval = LT_ENDIF; 809 else { 810 linestate = LS_DIRTY; 811 retval = LT_PLAIN; 812 } 813 cp = skipcomment(cp); 814 if (*cp != '\0') { 815 linestate = LS_DIRTY; 816 if (retval == LT_TRUE || retval == LT_FALSE || 817 retval == LT_TRUEI || retval == LT_FALSEI) 818 retval = LT_IF; 819 if (retval == LT_ELTRUE || retval == LT_ELFALSE) 820 retval = LT_ELIF; 821 } 822 if (retval != LT_PLAIN && (wascomment || incomment)) { 823 retval = linetype_2dodgy(retval); 824 if (incomment) 825 linestate = LS_DIRTY; 826 } 827 /* skipcomment normally changes the state, except 828 if the last line of the file lacks a newline, or 829 if there is too much whitespace in a directive */ 830 if (linestate == LS_HASH) { 831 size_t len = cp - tline; 832 if (fgets(tline + len, MAXLINE - len, input) == NULL) { 833 if (ferror(input)) 834 err(2, "can't read %s", filename); 835 /* append the missing newline at eof */ 836 strcpy(tline + len, newline); 837 cp += strlen(newline); 838 linestate = LS_START; 839 } else { 840 linestate = LS_DIRTY; 841 } 842 } 843 } 844 if (linestate == LS_DIRTY) { 845 while (*cp != '\0') 846 cp = skipcomment(cp + 1); 847 } 848 debug("parser line %d state %s comment %s line", linenum, 849 comment_name[incomment], linestate_name[linestate]); 850 return (retval); 851} 852 853/* 854 * These are the binary operators that are supported by the expression 855 * evaluator. 856 */ 857static Linetype op_strict(int *p, int v, Linetype at, Linetype bt) { 858 if(at == LT_IF || bt == LT_IF) return (LT_IF); 859 return (*p = v, v ? LT_TRUE : LT_FALSE); 860} 861static Linetype op_lt(int *p, Linetype at, int a, Linetype bt, int b) { 862 return op_strict(p, a < b, at, bt); 863} 864static Linetype op_gt(int *p, Linetype at, int a, Linetype bt, int b) { 865 return op_strict(p, a > b, at, bt); 866} 867static Linetype op_le(int *p, Linetype at, int a, Linetype bt, int b) { 868 return op_strict(p, a <= b, at, bt); 869} 870static Linetype op_ge(int *p, Linetype at, int a, Linetype bt, int b) { 871 return op_strict(p, a >= b, at, bt); 872} 873static Linetype op_eq(int *p, Linetype at, int a, Linetype bt, int b) { 874 return op_strict(p, a == b, at, bt); 875} 876static Linetype op_ne(int *p, Linetype at, int a, Linetype bt, int b) { 877 return op_strict(p, a != b, at, bt); 878} 879static Linetype op_or(int *p, Linetype at, int a, Linetype bt, int b) { 880 if (!strictlogic && (at == LT_TRUE || bt == LT_TRUE)) 881 return (*p = 1, LT_TRUE); 882 return op_strict(p, a || b, at, bt); 883} 884static Linetype op_and(int *p, Linetype at, int a, Linetype bt, int b) { 885 if (!strictlogic && (at == LT_FALSE || bt == LT_FALSE)) 886 return (*p = 0, LT_FALSE); 887 return op_strict(p, a && b, at, bt); 888} 889 890/* 891 * An evaluation function takes three arguments, as follows: (1) a pointer to 892 * an element of the precedence table which lists the operators at the current 893 * level of precedence; (2) a pointer to an integer which will receive the 894 * value of the expression; and (3) a pointer to a char* that points to the 895 * expression to be evaluated and that is updated to the end of the expression 896 * when evaluation is complete. The function returns LT_FALSE if the value of 897 * the expression is zero, LT_TRUE if it is non-zero, LT_IF if the expression 898 * depends on an unknown symbol, or LT_ERROR if there is a parse failure. 899 */ 900struct ops; 901 902typedef Linetype eval_fn(const struct ops *, int *, const char **); 903 904static eval_fn eval_table, eval_unary; 905 906/* 907 * The precedence table. Expressions involving binary operators are evaluated 908 * in a table-driven way by eval_table. When it evaluates a subexpression it 909 * calls the inner function with its first argument pointing to the next 910 * element of the table. Innermost expressions have special non-table-driven 911 * handling. 912 */ 913struct op { 914 const char *str; 915 Linetype (*fn)(int *, Linetype, int, Linetype, int); 916}; 917struct ops { 918 eval_fn *inner; 919 struct op op[5]; 920}; 921static const struct ops eval_ops[] = { 922 { eval_table, { { "||", op_or } } }, 923 { eval_table, { { "&&", op_and } } }, 924 { eval_table, { { "==", op_eq }, 925 { "!=", op_ne } } }, 926 { eval_unary, { { "<=", op_le }, 927 { ">=", op_ge }, 928 { "<", op_lt }, 929 { ">", op_gt } } } 930}; 931 932/* Current operator precedence level */ 933static int prec(const struct ops *ops) 934{ 935 return (ops - eval_ops); 936} 937 938/* 939 * Function for evaluating the innermost parts of expressions, 940 * viz. !expr (expr) number defined(symbol) symbol 941 * We reset the constexpr flag in the last two cases. 942 */ 943static Linetype 944eval_unary(const struct ops *ops, int *valp, const char **cpp) 945{ 946 const char *cp; 947 char *ep; 948 int sym; 949 bool defparen; 950 Linetype lt; 951 952 cp = skipcomment(*cpp); 953 if (*cp == '!') { 954 debug("eval%d !", prec(ops)); 955 cp++; 956 lt = eval_unary(ops, valp, &cp); 957 if (lt == LT_ERROR) 958 return (LT_ERROR); 959 if (lt != LT_IF) { 960 *valp = !*valp; 961 lt = *valp ? LT_TRUE : LT_FALSE; 962 } 963 } else if (*cp == '(') { 964 cp++; 965 debug("eval%d (", prec(ops)); 966 lt = eval_table(eval_ops, valp, &cp); 967 if (lt == LT_ERROR) 968 return (LT_ERROR); 969 cp = skipcomment(cp); 970 if (*cp++ != ')') 971 return (LT_ERROR); 972 } else if (isdigit((unsigned char)*cp)) { 973 debug("eval%d number", prec(ops)); 974 *valp = strtol(cp, &ep, 0); 975 if (ep == cp) 976 return (LT_ERROR); 977 lt = *valp ? LT_TRUE : LT_FALSE; 978 cp = skipsym(cp); 979 } else if (strncmp(cp, "defined", 7) == 0 && endsym(cp[7])) { 980 cp = skipcomment(cp+7); 981 debug("eval%d defined", prec(ops)); 982 if (*cp == '(') { 983 cp = skipcomment(cp+1); 984 defparen = true; 985 } else { 986 defparen = false; 987 } 988 sym = findsym(cp); 989 if (sym < 0) { 990 lt = LT_IF; 991 } else { 992 *valp = (value[sym] != NULL); 993 lt = *valp ? LT_TRUE : LT_FALSE; 994 } 995 cp = skipsym(cp); 996 cp = skipcomment(cp); 997 if (defparen && *cp++ != ')') 998 return (LT_ERROR); 999 constexpr = false; 1000 } else if (!endsym(*cp)) { 1001 debug("eval%d symbol", prec(ops)); 1002 sym = findsym(cp); 1003 cp = skipsym(cp); 1004 if (sym < 0) { 1005 lt = LT_IF; 1006 cp = skipargs(cp); 1007 } else if (value[sym] == NULL) { 1008 *valp = 0; 1009 lt = LT_FALSE; 1010 } else { 1011 *valp = strtol(value[sym], &ep, 0); 1012 if (*ep != '\0' || ep == value[sym]) 1013 return (LT_ERROR); 1014 lt = *valp ? LT_TRUE : LT_FALSE; 1015 cp = skipargs(cp); 1016 } 1017 constexpr = false; 1018 } else { 1019 debug("eval%d bad expr", prec(ops)); 1020 return (LT_ERROR); 1021 } 1022 1023 *cpp = cp; 1024 debug("eval%d = %d", prec(ops), *valp); 1025 return (lt); 1026} 1027 1028/* 1029 * Table-driven evaluation of binary operators. 1030 */ 1031static Linetype 1032eval_table(const struct ops *ops, int *valp, const char **cpp) 1033{ 1034 const struct op *op; 1035 const char *cp; 1036 int val; 1037 Linetype lt, rt; 1038 1039 debug("eval%d", prec(ops)); 1040 cp = *cpp; 1041 lt = ops->inner(ops+1, valp, &cp); 1042 if (lt == LT_ERROR) 1043 return (LT_ERROR); 1044 for (;;) { 1045 cp = skipcomment(cp); 1046 for (op = ops->op; op->str != NULL; op++) 1047 if (strncmp(cp, op->str, strlen(op->str)) == 0) 1048 break; 1049 if (op->str == NULL) 1050 break; 1051 cp += strlen(op->str); 1052 debug("eval%d %s", prec(ops), op->str); 1053 rt = ops->inner(ops+1, &val, &cp); 1054 if (rt == LT_ERROR) 1055 return (LT_ERROR); 1056 lt = op->fn(valp, lt, *valp, rt, val); 1057 } 1058 1059 *cpp = cp; 1060 debug("eval%d = %d", prec(ops), *valp); 1061 debug("eval%d lt = %s", prec(ops), linetype_name[lt]); 1062 return (lt); 1063} 1064 1065/* 1066 * Evaluate the expression on a #if or #elif line. If we can work out 1067 * the result we return LT_TRUE or LT_FALSE accordingly, otherwise we 1068 * return just a generic LT_IF. 1069 */ 1070static Linetype 1071ifeval(const char **cpp) 1072{ 1073 Linetype ret; 1074 int val = 0; 1075 1076 debug("eval %s", *cpp); 1077 constexpr = killconsts ? false : true; 1078 ret = eval_table(eval_ops, &val, cpp); 1079 debug("eval = %d", val); 1080 return (constexpr ? LT_IF : ret == LT_ERROR ? LT_IF : ret); 1081} 1082 1083/* 1084 * Skip over comments, strings, and character literals and stop at the 1085 * next character position that is not whitespace. Between calls we keep 1086 * the comment state in the global variable incomment, and we also adjust 1087 * the global variable linestate when we see a newline. 1088 * XXX: doesn't cope with the buffer splitting inside a state transition. 1089 */ 1090static const char * 1091skipcomment(const char *cp) 1092{ 1093 if (text || ignoring[depth]) { 1094 for (; isspace((unsigned char)*cp); cp++) 1095 if (*cp == '\n') 1096 linestate = LS_START; 1097 return (cp); 1098 } 1099 while (*cp != '\0') 1100 /* don't reset to LS_START after a line continuation */ 1101 if (strncmp(cp, "\\\r\n", 3) == 0) 1102 cp += 3; 1103 else if (strncmp(cp, "\\\n", 2) == 0) 1104 cp += 2; 1105 else switch (incomment) { 1106 case NO_COMMENT: 1107 if (strncmp(cp, "/\\\r\n", 4) == 0) { 1108 incomment = STARTING_COMMENT; 1109 cp += 4; 1110 } else if (strncmp(cp, "/\\\n", 3) == 0) { 1111 incomment = STARTING_COMMENT; 1112 cp += 3; 1113 } else if (strncmp(cp, "/*", 2) == 0) { 1114 incomment = C_COMMENT; 1115 cp += 2; 1116 } else if (strncmp(cp, "//", 2) == 0) { 1117 incomment = CXX_COMMENT; 1118 cp += 2; 1119 } else if (strncmp(cp, "\'", 1) == 0) { 1120 incomment = CHAR_LITERAL; 1121 linestate = LS_DIRTY; 1122 cp += 1; 1123 } else if (strncmp(cp, "\"", 1) == 0) { 1124 incomment = STRING_LITERAL; 1125 linestate = LS_DIRTY; 1126 cp += 1; 1127 } else if (strncmp(cp, "\n", 1) == 0) { 1128 linestate = LS_START; 1129 cp += 1; 1130 } else if (strchr(" \r\t", *cp) != NULL) { 1131 cp += 1; 1132 } else 1133 return (cp); 1134 continue; 1135 case CXX_COMMENT: 1136 if (strncmp(cp, "\n", 1) == 0) { 1137 incomment = NO_COMMENT; 1138 linestate = LS_START; 1139 } 1140 cp += 1; 1141 continue; 1142 case CHAR_LITERAL: 1143 case STRING_LITERAL: 1144 if ((incomment == CHAR_LITERAL && cp[0] == '\'') || 1145 (incomment == STRING_LITERAL && cp[0] == '\"')) { 1146 incomment = NO_COMMENT; 1147 cp += 1; 1148 } else if (cp[0] == '\\') { 1149 if (cp[1] == '\0') 1150 cp += 1; 1151 else 1152 cp += 2; 1153 } else if (strncmp(cp, "\n", 1) == 0) { 1154 if (incomment == CHAR_LITERAL) 1155 error("unterminated char literal"); 1156 else 1157 error("unterminated string literal"); 1158 } else 1159 cp += 1; 1160 continue; 1161 case C_COMMENT: 1162 if (strncmp(cp, "*\\\r\n", 4) == 0) { 1163 incomment = FINISHING_COMMENT; 1164 cp += 4; 1165 } else if (strncmp(cp, "*\\\n", 3) == 0) { 1166 incomment = FINISHING_COMMENT; 1167 cp += 3; 1168 } else if (strncmp(cp, "*/", 2) == 0) { 1169 incomment = NO_COMMENT; 1170 cp += 2; 1171 } else 1172 cp += 1; 1173 continue; 1174 case STARTING_COMMENT: 1175 if (*cp == '*') { 1176 incomment = C_COMMENT; 1177 cp += 1; 1178 } else if (*cp == '/') { 1179 incomment = CXX_COMMENT; 1180 cp += 1; 1181 } else { 1182 incomment = NO_COMMENT; 1183 linestate = LS_DIRTY; 1184 } 1185 continue; 1186 case FINISHING_COMMENT: 1187 if (*cp == '/') { 1188 incomment = NO_COMMENT; 1189 cp += 1; 1190 } else 1191 incomment = C_COMMENT; 1192 continue; 1193 default: 1194 abort(); /* bug */ 1195 } 1196 return (cp); 1197} 1198 1199/* 1200 * Skip macro arguments. 1201 */ 1202static const char * 1203skipargs(const char *cp) 1204{ 1205 const char *ocp = cp; 1206 int level = 0; 1207 cp = skipcomment(cp); 1208 if (*cp != '(') 1209 return (cp); 1210 do { 1211 if (*cp == '(') 1212 level++; 1213 if (*cp == ')') 1214 level--; 1215 cp = skipcomment(cp+1); 1216 } while (level != 0 && *cp != '\0'); 1217 if (level == 0) 1218 return (cp); 1219 else 1220 /* Rewind and re-detect the syntax error later. */ 1221 return (ocp); 1222} 1223 1224/* 1225 * Skip over an identifier. 1226 */ 1227static const char * 1228skipsym(const char *cp) 1229{ 1230 while (!endsym(*cp)) 1231 ++cp; 1232 return (cp); 1233} 1234 1235/* 1236 * Look for the symbol in the symbol table. If it is found, we return 1237 * the symbol table index, else we return -1. 1238 */ 1239static int 1240findsym(const char *str) 1241{ 1242 const char *cp; 1243 int symind; 1244 1245 cp = skipsym(str); 1246 if (cp == str) 1247 return (-1); 1248 if (symlist) { 1249 if (symdepth && firstsym) 1250 printf("%s%3d", zerosyms ? "" : "\n", depth); 1251 firstsym = zerosyms = false; 1252 printf("%s%.*s%s", 1253 symdepth ? " " : "", 1254 (int)(cp-str), str, 1255 symdepth ? "" : "\n"); 1256 /* we don't care about the value of the symbol */ 1257 return (0); 1258 } 1259 for (symind = 0; symind < nsyms; ++symind) { 1260 if (strlcmp(symname[symind], str, cp-str) == 0) { 1261 debug("findsym %s %s", symname[symind], 1262 value[symind] ? value[symind] : ""); 1263 return (symind); 1264 } 1265 } 1266 return (-1); 1267} 1268 1269/* 1270 * Add a symbol to the symbol table. 1271 */ 1272static void 1273addsym(bool ignorethis, bool definethis, char *sym) 1274{ 1275 int symind; 1276 char *val; 1277 1278 symind = findsym(sym); 1279 if (symind < 0) { 1280 if (nsyms >= MAXSYMS) 1281 errx(2, "too many symbols"); 1282 symind = nsyms++; 1283 } 1284 symname[symind] = sym; 1285 ignore[symind] = ignorethis; 1286 val = sym + (skipsym(sym) - sym); 1287 if (definethis) { 1288 if (*val == '=') { 1289 value[symind] = val+1; 1290 *val = '\0'; 1291 } else if (*val == '\0') 1292 value[symind] = "1"; 1293 else 1294 usage(); 1295 } else { 1296 if (*val != '\0') 1297 usage(); 1298 value[symind] = NULL; 1299 } 1300 debug("addsym %s=%s", symname[symind], 1301 value[symind] ? value[symind] : "undef"); 1302} 1303 1304/* 1305 * Compare s with n characters of t. 1306 * The same as strncmp() except that it checks that s[n] == '\0'. 1307 */ 1308static int 1309strlcmp(const char *s, const char *t, size_t n) 1310{ 1311 while (n-- && *t != '\0') 1312 if (*s != *t) 1313 return ((unsigned char)*s - (unsigned char)*t); 1314 else 1315 ++s, ++t; 1316 return ((unsigned char)*s); 1317} 1318 1319/* 1320 * Concatenate two strings into new memory, checking for failure. 1321 */ 1322static char * 1323astrcat(const char *s1, const char *s2) 1324{ 1325 char *s; 1326 int len; 1327 1328 len = 1 + snprintf(NULL, 0, "%s%s", s1, s2); 1329 s = (char *)malloc(len); 1330 if (s == NULL) 1331 err(2, "malloc"); 1332 snprintf(s, len, "%s%s", s1, s2); 1333 return (s); 1334} 1335 1336/* 1337 * Diagnostics. 1338 */ 1339static void 1340debug(const char *msg, ...) 1341{ 1342 va_list ap; 1343 1344 if (debugging) { 1345 va_start(ap, msg); 1346 vwarnx(msg, ap); 1347 va_end(ap); 1348 } 1349} 1350 1351static void 1352error(const char *msg) 1353{ 1354 if (depth == 0) 1355 warnx("%s: %d: %s", filename, linenum, msg); 1356 else 1357 warnx("%s: %d: %s (#if line %d depth %d)", 1358 filename, linenum, msg, stifline[depth], depth); 1359 closeout(); 1360 errx(2, "output may be truncated"); 1361} 1362