1/*- 2 * Copyright (c) 2010, 2013 The NetBSD Foundation, Inc. 3 * All rights reserved. 4 * 5 * This code is derived from software contributed to The NetBSD Foundation 6 * by David A. Holland. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 * POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30#include <assert.h> 31#include <stdlib.h> 32#include <string.h> 33#include <limits.h> 34#include <errno.h> 35 36#include "bool.h" 37#include "utils.h" 38#include "mode.h" 39#include "place.h" 40#include "files.h" 41#include "directive.h" 42#include "macro.h" 43#include "eval.h" 44#include "output.h" 45 46struct ifstate { 47 struct ifstate *prev; 48 struct place startplace; 49 bool curtrue; 50 bool evertrue; 51 bool seenelse; 52}; 53 54static struct ifstate *ifstate; 55 56//////////////////////////////////////////////////////////// 57// common parsing bits 58 59static 60void 61uncomment(char *buf) 62{ 63 char *s, *t, *u = NULL; 64 bool incomment = false; 65 bool inesc = false; 66 bool inquote = false; 67 char quote = '\0'; 68 69 for (s = t = buf; *s; s++) { 70 if (incomment) { 71 if (s[0] == '*' && s[1] == '/') { 72 s++; 73 incomment = false; 74 } 75 } else { 76 if (!inquote && s[0] == '/' && s[1] == '*') { 77 incomment = true; 78 } else { 79 if (inesc) { 80 inesc = false; 81 } else if (s[0] == '\\') { 82 inesc = true; 83 } else if (!inquote && 84 (s[0] == '"' || s[0] == '\'')) { 85 inquote = true; 86 quote = s[0]; 87 } else if (inquote && s[0] == quote) { 88 inquote = false; 89 } 90 91 if (t != s) { 92 *t = *s; 93 } 94 if (!strchr(ws, *t)) { 95 u = t; 96 } 97 t++; 98 } 99 } 100 } 101 if (u) { 102 /* end string after last non-whitespace char */ 103 u[1] = '\0'; 104 } else { 105 *t = '\0'; 106 } 107} 108 109static 110void 111oneword(const char *what, struct place *p2, char *line) 112{ 113 size_t pos; 114 115 pos = strcspn(line, ws); 116 if (line[pos] != '\0') { 117 place_addcolumns(p2, pos); 118 complain(p2, "Garbage after %s argument", what); 119 complain_fail(); 120 line[pos] = '\0'; 121 } 122} 123 124//////////////////////////////////////////////////////////// 125// if handling 126 127static 128struct ifstate * 129ifstate_create(struct ifstate *prev, struct place *p, bool startstate) 130{ 131 struct ifstate *is; 132 133 is = domalloc(sizeof(*is)); 134 is->prev = prev; 135 if (p != NULL) { 136 is->startplace = *p; 137 } else { 138 place_setbuiltin(&is->startplace, 1); 139 } 140 is->curtrue = startstate; 141 is->evertrue = is->curtrue; 142 is->seenelse = false; 143 return is; 144} 145 146static 147void 148ifstate_destroy(struct ifstate *is) 149{ 150 dofree(is, sizeof(*is)); 151} 152 153static 154void 155ifstate_push(struct place *p, bool startstate) 156{ 157 struct ifstate *newstate; 158 159 newstate = ifstate_create(ifstate, p, startstate); 160 if (!ifstate->curtrue) { 161 newstate->curtrue = false; 162 newstate->evertrue = true; 163 } 164 ifstate = newstate; 165} 166 167static 168void 169ifstate_pop(void) 170{ 171 struct ifstate *is; 172 173 is = ifstate; 174 ifstate = ifstate->prev; 175 ifstate_destroy(is); 176} 177 178static 179void 180d_if(struct lineplace *lp, struct place *p2, char *line) 181{ 182 bool doprint; 183 char *expr; 184 bool val; 185 struct place p3 = *p2; 186 size_t oldlen; 187 188 doprint = ifstate->curtrue; 189 190 expr = macroexpand(p2, line, strlen(line), true); 191 192 oldlen = strlen(expr); 193 uncomment(expr); 194 /* trim to fit, so the malloc debugging won't complain */ 195 expr = dorealloc(expr, oldlen + 1, strlen(expr) + 1); 196 197 if (ifstate->curtrue) { 198 val = eval(&p3, expr); 199 } else { 200 val = 0; 201 } 202 ifstate_push(&lp->current, val); 203 dostrfree(expr); 204 205 if (doprint) { 206 debuglog(&lp->current, "#if: %s", 207 ifstate->curtrue ? "taken" : "not taken"); 208 } 209} 210 211static 212void 213d_ifdef(struct lineplace *lp, struct place *p2, char *line) 214{ 215 bool doprint; 216 217 doprint = ifstate->curtrue; 218 219 uncomment(line); 220 oneword("#ifdef", p2, line); 221 ifstate_push(&lp->current, macro_isdefined(line)); 222 223 if (doprint) { 224 debuglog(&lp->current, "#ifdef %s: %s", 225 line, ifstate->curtrue ? "taken" : "not taken"); 226 } 227} 228 229static 230void 231d_ifndef(struct lineplace *lp, struct place *p2, char *line) 232{ 233 bool doprint; 234 235 doprint = ifstate->curtrue; 236 237 uncomment(line); 238 oneword("#ifndef", p2, line); 239 ifstate_push(&lp->current, !macro_isdefined(line)); 240 241 if (doprint) { 242 debuglog(&lp->current, "#ifndef %s: %s", 243 line, ifstate->curtrue ? "taken" : "not taken"); 244 } 245} 246 247static 248void 249d_elif(struct lineplace *lp, struct place *p2, char *line) 250{ 251 bool doprint; 252 char *expr; 253 struct place p3 = *p2; 254 size_t oldlen; 255 256 if (ifstate->seenelse) { 257 complain(&lp->current, "#elif after #else"); 258 complain_fail(); 259 } 260 261 doprint = ifstate->curtrue; 262 263 if (ifstate->evertrue) { 264 ifstate->curtrue = false; 265 } else { 266 expr = macroexpand(p2, line, strlen(line), true); 267 268 oldlen = strlen(expr); 269 uncomment(expr); 270 /* trim to fit, so the malloc debugging won't complain */ 271 expr = dorealloc(expr, oldlen + 1, strlen(expr) + 1); 272 273 ifstate->curtrue = eval(&p3, expr); 274 ifstate->evertrue = ifstate->curtrue; 275 dostrfree(expr); 276 } 277 278 if (doprint) { 279 debuglog2(&lp->current, &ifstate->startplace, "#elif: %s", 280 ifstate->curtrue ? "taken" : "not taken"); 281 } 282} 283 284static 285void 286d_else(struct lineplace *lp, struct place *p2, char *line) 287{ 288 bool doprint; 289 290 (void)p2; 291 (void)line; 292 293 if (ifstate->seenelse) { 294 complain(&lp->current, 295 "Multiple #else directives in one conditional"); 296 complain_fail(); 297 } 298 299 doprint = ifstate->curtrue; 300 301 ifstate->curtrue = !ifstate->evertrue; 302 ifstate->evertrue = true; 303 ifstate->seenelse = true; 304 305 if (doprint) { 306 debuglog2(&lp->current, &ifstate->startplace, "#else: %s", 307 ifstate->curtrue ? "taken" : "not taken"); 308 } 309} 310 311static 312void 313d_endif(struct lineplace *lp, struct place *p2, char *line) 314{ 315 (void)p2; 316 (void)line; 317 318 if (ifstate->prev == NULL) { 319 complain(&lp->current, "Unmatched #endif"); 320 complain_fail(); 321 } else { 322 debuglog2(&lp->current, &ifstate->startplace, "#endif"); 323 ifstate_pop(); 324 } 325} 326 327//////////////////////////////////////////////////////////// 328// macros 329 330static 331void 332d_define(struct lineplace *lp, struct place *p2, char *line) 333{ 334 size_t pos, argpos; 335 struct place p3, p4; 336 337 (void)lp; 338 339 /* 340 * line may be: 341 * macro expansion 342 * macro(arg, arg, ...) expansion 343 */ 344 345 pos = strcspn(line, " \t\f\v("); 346 if (line[pos] == '(') { 347 line[pos++] = '\0'; 348 argpos = pos; 349 pos = pos + strcspn(line+pos, "()"); 350 if (line[pos] == '(') { 351 place_addcolumns(p2, pos); 352 complain(p2, "Left parenthesis in macro parameters"); 353 complain_fail(); 354 return; 355 } 356 if (line[pos] != ')') { 357 place_addcolumns(p2, pos); 358 complain(p2, "Unclosed macro parameter list"); 359 complain_fail(); 360 return; 361 } 362 line[pos++] = '\0'; 363#if 0 364 if (!strchr(ws, line[pos])) { 365 p2->column += pos; 366 complain(p2, "Trash after macro parameter list"); 367 complain_fail(); 368 return; 369 } 370#endif 371 } else if (line[pos] == '\0') { 372 argpos = 0; 373 } else { 374 line[pos++] = '\0'; 375 argpos = 0; 376 } 377 378 pos += strspn(line+pos, ws); 379 380 p3 = *p2; 381 place_addcolumns(&p3, argpos); 382 383 p4 = *p2; 384 place_addcolumns(&p4, pos); 385 386 if (argpos) { 387 debuglog(&lp->current, "Defining %s()", line); 388 macro_define_params(p2, line, &p3, 389 line + argpos, &p4, 390 line + pos); 391 } else { 392 debuglog(&lp->current, "Defining %s", line); 393 macro_define_plain(p2, line, &p4, line + pos); 394 } 395} 396 397static 398void 399d_undef(struct lineplace *lp, struct place *p2, char *line) 400{ 401 (void)lp; 402 403 uncomment(line); 404 oneword("#undef", p2, line); 405 debuglog(&lp->current, "Undef %s", line); 406 macro_undef(line); 407} 408 409//////////////////////////////////////////////////////////// 410// includes 411 412static 413bool 414tryinclude(struct place *p, char *line) 415{ 416 size_t len; 417 418 len = strlen(line); 419 if (len > 2 && line[0] == '"' && line[len-1] == '"') { 420 line[len-1] = '\0'; 421 debuglog(p, "Entering include file \"%s\"", line+1); 422 file_readquote(p, line+1); 423 debuglog(p, "Leaving include file \"%s\"", line+1); 424 line[len-1] = '"'; 425 return true; 426 } 427 if (len > 2 && line[0] == '<' && line[len-1] == '>') { 428 line[len-1] = '\0'; 429 debuglog(p, "Entering include file <%s>", line+1); 430 file_readbracket(p, line+1); 431 debuglog(p, "Leaving include file <%s>", line+1); 432 line[len-1] = '>'; 433 return true; 434 } 435 return false; 436} 437 438static 439void 440d_include(struct lineplace *lp, struct place *p2, char *line) 441{ 442 char *text; 443 size_t oldlen; 444 445 uncomment(line); 446 if (tryinclude(&lp->current, line)) { 447 return; 448 } 449 text = macroexpand(p2, line, strlen(line), false); 450 451 oldlen = strlen(text); 452 uncomment(text); 453 /* trim to fit, so the malloc debugging won't complain */ 454 text = dorealloc(text, oldlen + 1, strlen(text) + 1); 455 456 if (tryinclude(&lp->current, text)) { 457 dostrfree(text); 458 return; 459 } 460 complain(&lp->current, "Illegal #include directive"); 461 complain(&lp->current, "Before macro expansion: #include %s", line); 462 complain(&lp->current, "After macro expansion: #include %s", text); 463 dostrfree(text); 464 complain_fail(); 465} 466 467static 468void 469d_line(struct lineplace *lp, struct place *p2, char *line) 470{ 471 char *text; 472 size_t oldlen; 473 unsigned long val; 474 char *moretext; 475 size_t moretextlen; 476 char *filename; 477 478 text = macroexpand(p2, line, strlen(line), true); 479 480 oldlen = strlen(text); 481 uncomment(text); 482 /* trim to fit, so the malloc debugging won't complain */ 483 text = dorealloc(text, oldlen + 1, strlen(text) + 1); 484 485 /* 486 * What we should have here: either 1234 "file.c", 487 * or just 1234. 488 */ 489 490 errno = 0; 491 val = strtoul(text, &moretext, 10); 492 if (errno) { 493 complain(&lp->current, 494 "Invalid line number in #line directive"); 495 goto fail; 496 } 497#if UINT_MAX < ULONG_MAX 498 if (val > UINT_MAX) { 499 complain(&lp->current, 500 "Line number in #line directive too large"); 501 goto fail; 502 } 503#endif 504 moretext += strspn(moretext, ws); 505 moretextlen = strlen(moretext); 506 place_addcolumns(&lp->current, moretext - text); 507 508 if (moretextlen > 2 && 509 moretext[0] == '"' && moretext[moretextlen-1] == '"') { 510 filename = dostrndup(moretext+1, moretextlen-2); 511 place_changefile(&lp->nextline, filename); 512 dostrfree(filename); 513 } 514 else if (moretextlen > 0) { 515 complain(&lp->current, 516 "Invalid file name in #line directive"); 517 goto fail; 518 } 519 520 lp->nextline.line = val; 521 dostrfree(text); 522 return; 523 524fail: 525 complain(&lp->current, "Before macro expansion: #line %s", line); 526 complain(&lp->current, "After macro expansion: #line %s", text); 527 complain_fail(); 528 dostrfree(text); 529} 530 531//////////////////////////////////////////////////////////// 532// messages 533 534static 535void 536d_warning(struct lineplace *lp, struct place *p2, char *line) 537{ 538 char *msg; 539 540 msg = macroexpand(p2, line, strlen(line), false); 541 complain(&lp->current, "#warning: %s", msg); 542 if (mode.werror) { 543 complain_fail(); 544 } 545 dostrfree(msg); 546} 547 548static 549void 550d_error(struct lineplace *lp, struct place *p2, char *line) 551{ 552 char *msg; 553 554 msg = macroexpand(p2, line, strlen(line), false); 555 complain(&lp->current, "#error: %s", msg); 556 complain_fail(); 557 dostrfree(msg); 558} 559 560//////////////////////////////////////////////////////////// 561// other 562 563static 564void 565d_pragma(struct lineplace *lp, struct place *p2, char *line) 566{ 567 (void)p2; 568 569 complain(&lp->current, "#pragma %s", line); 570 complain_fail(); 571} 572 573//////////////////////////////////////////////////////////// 574// directive table 575 576static const struct { 577 const char *name; 578 bool ifskip; 579 void (*func)(struct lineplace *, struct place *, char *line); 580} directives[] = { 581 { "define", true, d_define }, 582 { "elif", false, d_elif }, 583 { "else", false, d_else }, 584 { "endif", false, d_endif }, 585 { "error", true, d_error }, 586 { "if", false, d_if }, 587 { "ifdef", false, d_ifdef }, 588 { "ifndef", false, d_ifndef }, 589 { "include", true, d_include }, 590 { "line", true, d_line }, 591 { "pragma", true, d_pragma }, 592 { "undef", true, d_undef }, 593 { "warning", true, d_warning }, 594}; 595static const unsigned numdirectives = HOWMANY(directives); 596 597static 598void 599directive_gotdirective(struct lineplace *lp, char *line) 600{ 601 struct place p2; 602 size_t len, skip; 603 unsigned i; 604 605 p2 = lp->current; 606 for (i=0; i<numdirectives; i++) { 607 len = strlen(directives[i].name); 608 if (!strncmp(line, directives[i].name, len) && 609 strchr(ws, line[len])) { 610 if (directives[i].ifskip && !ifstate->curtrue) { 611 return; 612 } 613 skip = len + strspn(line+len, ws); 614 place_addcolumns(&p2, skip); 615 line += skip; 616 617 len = strlen(line); 618 len = notrailingws(line, len); 619 if (len < strlen(line)) { 620 line[len] = '\0'; 621 } 622 directives[i].func(lp, &p2, line); 623 return; 624 } 625 } 626 /* ugh. allow # by itself, including with a comment after it */ 627 uncomment(line); 628 if (line[0] == '\0') { 629 return; 630 } 631 632 skip = strcspn(line, ws); 633 complain(&lp->current, "Unknown directive #%.*s", (int)skip, line); 634 complain_fail(); 635} 636 637/* 638 * Check for nested comment delimiters in LINE. 639 */ 640static 641size_t 642directive_scancomments(const struct lineplace *lp, char *line, size_t len) 643{ 644 size_t pos; 645 bool incomment; 646 struct place p2; 647 648 p2 = lp->current; 649 incomment = 0; 650 for (pos = 0; pos+1 < len; pos++) { 651 if (line[pos] == '/' && line[pos+1] == '*') { 652 if (incomment) { 653 complain(&p2, "Warning: %c%c within comment", 654 '/', '*'); 655 if (mode.werror) { 656 complain_failed(); 657 } 658 } else { 659 incomment = true; 660 } 661 pos++; 662 } else if (line[pos] == '*' && line[pos+1] == '/') { 663 if (incomment) { 664 incomment = false; 665 } else { 666 /* stray end-comment; should we care? */ 667 } 668 pos++; 669 } 670 if (line[pos] == '\n') { 671 place_addlines(&p2, 1); 672 p2.column = 0; 673 } else { 674 place_addcolumns(&p2, 1); 675 } 676 } 677 678 /* multiline comments are supposed to arrive in a single buffer */ 679 assert(!incomment); 680 return len; 681} 682 683void 684directive_gotline(struct lineplace *lp, char *line, size_t len) 685{ 686 size_t skip; 687 688 if (warns.nestcomment) { 689 directive_scancomments(lp, line, len); 690 } 691 692 /* check if we have a directive line (# exactly in column 0) */ 693 if (len > 0 && line[0] == '#') { 694 skip = 1 + strspn(line + 1, ws); 695 assert(skip <= len); 696 place_addcolumns(&lp->current, skip); 697 assert(line[len] == '\0'); 698 directive_gotdirective(lp, line+skip /*, length = len-skip */); 699 place_addcolumns(&lp->current, len-skip); 700 } else if (ifstate->curtrue) { 701 macro_sendline(&lp->current, line, len); 702 place_addcolumns(&lp->current, len); 703 } 704} 705 706void 707directive_goteof(struct place *p) 708{ 709 while (ifstate->prev != NULL) { 710 complain(p, "Missing #endif"); 711 complain(&ifstate->startplace, "...opened at this point"); 712 complain_failed(); 713 ifstate_pop(); 714 } 715 macro_sendeof(p); 716} 717 718//////////////////////////////////////////////////////////// 719// module initialization 720 721void 722directive_init(void) 723{ 724 ifstate = ifstate_create(NULL, NULL, true); 725} 726 727void 728directive_cleanup(void) 729{ 730 assert(ifstate->prev == NULL); 731 ifstate_destroy(ifstate); 732 ifstate = NULL; 733} 734