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 <stdbool.h> 32 #include <stdlib.h> 33 #include <string.h> 34 35 #include "utils.h" 36 #include "mode.h" 37 #include "place.h" 38 #include "files.h" 39 #include "directive.h" 40 #include "macro.h" 41 #include "eval.h" 42 #include "output.h" 43 44 struct ifstate { 45 struct ifstate *prev; 46 struct place startplace; 47 bool curtrue; 48 bool evertrue; 49 bool seenelse; 50 }; 51 52 static struct ifstate *ifstate; 53 54 //////////////////////////////////////////////////////////// 55 // common parsing bits 56 57 static 58 void 59 uncomment(char *buf) 60 { 61 char *s, *t, *u = NULL; 62 bool incomment = false; 63 bool inesc = false; 64 bool inquote = false; 65 char quote = '\0'; 66 67 for (s = t = buf; *s; s++) { 68 if (incomment) { 69 if (s[0] == '*' && s[1] == '/') { 70 s++; 71 incomment = false; 72 } 73 } else { 74 if (!inquote && s[0] == '/' && s[1] == '*') { 75 incomment = true; 76 } else { 77 if (inesc) { 78 inesc = false; 79 } else if (s[0] == '\\') { 80 inesc = true; 81 } else if (!inquote && 82 (s[0] == '"' || s[0] == '\'')) { 83 inquote = true; 84 quote = s[0]; 85 } else if (inquote && s[0] == quote) { 86 inquote = false; 87 } 88 89 if (t != s) { 90 *t = *s; 91 } 92 if (!strchr(ws, *t)) { 93 u = t; 94 } 95 t++; 96 } 97 } 98 } 99 if (u) { 100 /* end string after last non-whitespace char */ 101 u[1] = '\0'; 102 } else { 103 *t = '\0'; 104 } 105 } 106 107 static 108 void 109 oneword(const char *what, struct place *p2, char *line) 110 { 111 size_t pos; 112 113 pos = strcspn(line, ws); 114 if (line[pos] != '\0') { 115 p2->column += pos; 116 complain(p2, "Garbage after %s argument", what); 117 complain_fail(); 118 line[pos] = '\0'; 119 } 120 } 121 122 //////////////////////////////////////////////////////////// 123 // if handling 124 125 static 126 struct ifstate * 127 ifstate_create(struct ifstate *prev, struct place *p, bool startstate) 128 { 129 struct ifstate *is; 130 131 is = domalloc(sizeof(*is)); 132 is->prev = prev; 133 if (p != NULL) { 134 is->startplace = *p; 135 } else { 136 place_setbuiltin(&is->startplace, 1); 137 } 138 is->curtrue = startstate; 139 is->evertrue = is->curtrue; 140 is->seenelse = false; 141 return is; 142 } 143 144 static 145 void 146 ifstate_destroy(struct ifstate *is) 147 { 148 dofree(is, sizeof(*is)); 149 } 150 151 static 152 void 153 ifstate_push(struct place *p, bool startstate) 154 { 155 struct ifstate *newstate; 156 157 newstate = ifstate_create(ifstate, p, startstate); 158 if (!ifstate->curtrue) { 159 newstate->curtrue = false; 160 newstate->evertrue = true; 161 } 162 ifstate = newstate; 163 } 164 165 static 166 void 167 ifstate_pop(void) 168 { 169 struct ifstate *is; 170 171 is = ifstate; 172 ifstate = ifstate->prev; 173 ifstate_destroy(is); 174 } 175 176 static 177 void 178 d_if(struct place *p, struct place *p2, char *line) 179 { 180 char *expr; 181 bool val; 182 struct place p3 = *p2; 183 size_t oldlen; 184 185 expr = macroexpand(p2, line, strlen(line), true); 186 187 oldlen = strlen(expr); 188 uncomment(expr); 189 /* trim to fit, so the malloc debugging won't complain */ 190 expr = dorealloc(expr, oldlen + 1, strlen(expr) + 1); 191 192 if (ifstate->curtrue) { 193 val = eval(&p3, expr); 194 } else { 195 val = 0; 196 } 197 ifstate_push(p, val); 198 dostrfree(expr); 199 } 200 201 static 202 void 203 d_ifdef(struct place *p, struct place *p2, char *line) 204 { 205 uncomment(line); 206 oneword("#ifdef", p2, line); 207 ifstate_push(p, macro_isdefined(line)); 208 } 209 210 static 211 void 212 d_ifndef(struct place *p, struct place *p2, char *line) 213 { 214 uncomment(line); 215 oneword("#ifndef", p2, line); 216 ifstate_push(p, !macro_isdefined(line)); 217 } 218 219 static 220 void 221 d_elif(struct place *p, struct place *p2, char *line) 222 { 223 char *expr; 224 struct place p3 = *p2; 225 size_t oldlen; 226 227 if (ifstate->seenelse) { 228 complain(p, "#elif after #else"); 229 complain_fail(); 230 } 231 232 if (ifstate->evertrue) { 233 ifstate->curtrue = false; 234 } else { 235 expr = macroexpand(p2, line, strlen(line), true); 236 237 oldlen = strlen(expr); 238 uncomment(expr); 239 /* trim to fit, so the malloc debugging won't complain */ 240 expr = dorealloc(expr, oldlen + 1, strlen(expr) + 1); 241 242 ifstate->curtrue = eval(&p3, expr); 243 ifstate->evertrue = ifstate->curtrue; 244 dostrfree(expr); 245 } 246 } 247 248 static 249 void 250 d_else(struct place *p, struct place *p2, char *line) 251 { 252 (void)p2; 253 (void)line; 254 255 if (ifstate->seenelse) { 256 complain(p, "Multiple #else directives in one conditional"); 257 complain_fail(); 258 } 259 260 ifstate->curtrue = !ifstate->evertrue; 261 ifstate->evertrue = true; 262 ifstate->seenelse = true; 263 } 264 265 static 266 void 267 d_endif(struct place *p, struct place *p2, char *line) 268 { 269 (void)p2; 270 (void)line; 271 272 if (ifstate->prev == NULL) { 273 complain(p, "Unmatched #endif"); 274 complain_fail(); 275 } else { 276 ifstate_pop(); 277 } 278 } 279 280 //////////////////////////////////////////////////////////// 281 // macros 282 283 static 284 void 285 d_define(struct place *p, struct place *p2, char *line) 286 { 287 size_t pos, argpos; 288 struct place p3, p4; 289 290 (void)p; 291 292 /* 293 * line may be: 294 * macro expansion 295 * macro(arg, arg, ...) expansion 296 */ 297 298 pos = strcspn(line, " \t\f\v("); 299 if (line[pos] == '(') { 300 line[pos++] = '\0'; 301 argpos = pos; 302 pos = pos + strcspn(line+pos, "()"); 303 if (line[pos] == '(') { 304 p2->column += pos; 305 complain(p2, "Left parenthesis in macro parameters"); 306 complain_fail(); 307 return; 308 } 309 if (line[pos] != ')') { 310 p2->column += pos; 311 complain(p2, "Unclosed macro parameter list"); 312 complain_fail(); 313 return; 314 } 315 line[pos++] = '\0'; 316 #if 0 317 if (!strchr(ws, line[pos])) { 318 p2->column += pos; 319 complain(p2, "Trash after macro parameter list"); 320 complain_fail(); 321 return; 322 } 323 #endif 324 } else if (line[pos] == '\0') { 325 argpos = 0; 326 } else { 327 line[pos++] = '\0'; 328 argpos = 0; 329 } 330 331 pos += strspn(line+pos, ws); 332 333 p3 = *p2; 334 p3.column += argpos; 335 336 p4 = *p2; 337 p4.column += pos; 338 339 if (argpos) { 340 macro_define_params(p2, line, &p3, 341 line + argpos, &p4, 342 line + pos); 343 } else { 344 macro_define_plain(p2, line, &p4, line + pos); 345 } 346 } 347 348 static 349 void 350 d_undef(struct place *p, struct place *p2, char *line) 351 { 352 (void)p; 353 354 uncomment(line); 355 oneword("#undef", p2, line); 356 macro_undef(line); 357 } 358 359 //////////////////////////////////////////////////////////// 360 // includes 361 362 static 363 bool 364 tryinclude(struct place *p, char *line) 365 { 366 size_t len; 367 368 len = strlen(line); 369 if (len > 2 && line[0] == '"' && line[len-1] == '"') { 370 line[len-1] = '\0'; 371 file_readquote(p, line+1); 372 line[len-1] = '"'; 373 return true; 374 } 375 if (len > 2 && line[0] == '<' && line[len-1] == '>') { 376 line[len-1] = '\0'; 377 file_readbracket(p, line+1); 378 line[len-1] = '>'; 379 return true; 380 } 381 return false; 382 } 383 384 static 385 void 386 d_include(struct place *p, struct place *p2, char *line) 387 { 388 char *text; 389 size_t oldlen; 390 391 uncomment(line); 392 if (tryinclude(p, line)) { 393 return; 394 } 395 text = macroexpand(p2, line, strlen(line), false); 396 397 oldlen = strlen(text); 398 uncomment(text); 399 /* trim to fit, so the malloc debugging won't complain */ 400 text = dorealloc(text, oldlen + 1, strlen(text) + 1); 401 402 if (tryinclude(p, text)) { 403 dostrfree(text); 404 return; 405 } 406 complain(p, "Illegal #include directive"); 407 complain(p, "Before macro expansion: #include %s", line); 408 complain(p, "After macro expansion: #include %s", text); 409 dostrfree(text); 410 complain_fail(); 411 } 412 413 static 414 void 415 d_line(struct place *p, struct place *p2, char *line) 416 { 417 (void)p2; 418 (void)line; 419 420 /* XXX */ 421 complain(p, "Sorry, no #line yet"); 422 } 423 424 //////////////////////////////////////////////////////////// 425 // messages 426 427 static 428 void 429 d_warning(struct place *p, struct place *p2, char *line) 430 { 431 char *msg; 432 433 msg = macroexpand(p2, line, strlen(line), false); 434 complain(p, "#warning: %s", msg); 435 if (mode.werror) { 436 complain_fail(); 437 } 438 dostrfree(msg); 439 } 440 441 static 442 void 443 d_error(struct place *p, struct place *p2, char *line) 444 { 445 char *msg; 446 447 msg = macroexpand(p2, line, strlen(line), false); 448 complain(p, "#error: %s", msg); 449 complain_fail(); 450 dostrfree(msg); 451 } 452 453 //////////////////////////////////////////////////////////// 454 // other 455 456 static 457 void 458 d_pragma(struct place *p, struct place *p2, char *line) 459 { 460 (void)p2; 461 462 complain(p, "#pragma %s", line); 463 complain_fail(); 464 } 465 466 //////////////////////////////////////////////////////////// 467 // directive table 468 469 static const struct { 470 const char *name; 471 bool ifskip; 472 void (*func)(struct place *, struct place *, char *line); 473 } directives[] = { 474 { "define", true, d_define }, 475 { "elif", false, d_elif }, 476 { "else", false, d_else }, 477 { "endif", false, d_endif }, 478 { "error", true, d_error }, 479 { "if", false, d_if }, 480 { "ifdef", false, d_ifdef }, 481 { "ifndef", false, d_ifndef }, 482 { "include", true, d_include }, 483 { "line", true, d_line }, 484 { "pragma", true, d_pragma }, 485 { "undef", true, d_undef }, 486 { "warning", true, d_warning }, 487 }; 488 static const unsigned numdirectives = HOWMANY(directives); 489 490 static 491 void 492 directive_gotdirective(struct place *p, char *line) 493 { 494 struct place p2; 495 size_t len, skip; 496 unsigned i; 497 498 p2 = *p; 499 for (i=0; i<numdirectives; i++) { 500 len = strlen(directives[i].name); 501 if (!strncmp(line, directives[i].name, len) && 502 strchr(ws, line[len])) { 503 if (directives[i].ifskip && !ifstate->curtrue) { 504 return; 505 } 506 skip = len + strspn(line+len, ws); 507 p2.column += skip; 508 line += skip; 509 510 len = strlen(line); 511 len = notrailingws(line, len); 512 if (len < strlen(line)) { 513 line[len] = '\0'; 514 } 515 directives[i].func(p, &p2, line); 516 return; 517 } 518 } 519 /* ugh. allow # by itself, including with a comment after it */ 520 uncomment(line); 521 if (line[0] == '\0') { 522 return; 523 } 524 525 skip = strcspn(line, ws); 526 complain(p, "Unknown directive #%.*s", (int)skip, line); 527 complain_fail(); 528 } 529 530 /* 531 * Check for nested comment delimiters in LINE. 532 */ 533 static 534 size_t 535 directive_scancomments(const struct place *p, char *line, size_t len) 536 { 537 size_t pos; 538 bool incomment; 539 struct place p2; 540 541 p2 = *p; 542 incomment = 0; 543 for (pos = 0; pos+1 < len; pos++) { 544 if (line[pos] == '/' && line[pos+1] == '*') { 545 if (incomment) { 546 complain(&p2, "Warning: %c%c within comment", 547 '/', '*'); 548 if (mode.werror) { 549 complain_failed(); 550 } 551 } else { 552 incomment = true; 553 } 554 pos++; 555 } else if (line[pos] == '*' && line[pos+1] == '/') { 556 if (incomment) { 557 incomment = false; 558 } else { 559 /* stray end-comment; should we care? */ 560 } 561 pos++; 562 } 563 if (line[pos] == '\n') { 564 p2.line++; 565 p2.column = 0; 566 } else { 567 p2.column++; 568 } 569 } 570 571 /* multiline comments are supposed to arrive in a single buffer */ 572 assert(!incomment); 573 return len; 574 } 575 576 void 577 directive_gotline(struct place *p, char *line, size_t len) 578 { 579 size_t skip; 580 581 if (warns.nestcomment) { 582 directive_scancomments(p, line, len); 583 } 584 585 /* check if we have a directive line (# exactly in column 0) */ 586 if (line[0] == '#') { 587 skip = 1 + strspn(line + 1, ws); 588 assert(skip <= len); 589 p->column += skip; 590 assert(line[len] == '\0'); 591 directive_gotdirective(p, line+skip /*, length = len-skip */); 592 p->column += len-skip; 593 } else if (ifstate->curtrue) { 594 macro_sendline(p, line, len); 595 p->column += len; 596 } 597 } 598 599 void 600 directive_goteof(struct place *p) 601 { 602 while (ifstate->prev != NULL) { 603 complain(p, "Missing #endif"); 604 complain(&ifstate->startplace, "...opened at this point"); 605 complain_failed(); 606 ifstate_pop(); 607 } 608 macro_sendeof(p); 609 } 610 611 //////////////////////////////////////////////////////////// 612 // module initialization 613 614 void 615 directive_init(void) 616 { 617 ifstate = ifstate_create(NULL, NULL, true); 618 } 619 620 void 621 directive_cleanup(void) 622 { 623 assert(ifstate->prev == NULL); 624 ifstate_destroy(ifstate); 625 ifstate = NULL; 626 } 627