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
46 struct ifstate {
47 struct ifstate *prev;
48 struct place startplace;
49 bool curtrue;
50 bool evertrue;
51 bool seenelse;
52 };
53
54 static struct ifstate *ifstate;
55
56 ////////////////////////////////////////////////////////////
57 // common parsing bits
58
59 static
60 void
uncomment(char * buf)61 uncomment(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
109 static
110 void
oneword(const char * what,struct place * p2,char * line)111 oneword(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
127 static
128 struct ifstate *
ifstate_create(struct ifstate * prev,struct place * p,bool startstate)129 ifstate_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
146 static
147 void
ifstate_destroy(struct ifstate * is)148 ifstate_destroy(struct ifstate *is)
149 {
150 dofree(is, sizeof(*is));
151 }
152
153 static
154 void
ifstate_push(struct place * p,bool startstate)155 ifstate_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
167 static
168 void
ifstate_pop(void)169 ifstate_pop(void)
170 {
171 struct ifstate *is;
172
173 is = ifstate;
174 ifstate = ifstate->prev;
175 ifstate_destroy(is);
176 }
177
178 static
179 void
d_if(struct lineplace * lp,struct place * p2,char * line)180 d_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
211 static
212 void
d_ifdef(struct lineplace * lp,struct place * p2,char * line)213 d_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
229 static
230 void
d_ifndef(struct lineplace * lp,struct place * p2,char * line)231 d_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
247 static
248 void
d_elif(struct lineplace * lp,struct place * p2,char * line)249 d_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
284 static
285 void
d_else(struct lineplace * lp,struct place * p2,char * line)286 d_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
311 static
312 void
d_endif(struct lineplace * lp,struct place * p2,char * line)313 d_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
330 static
331 void
d_define(struct lineplace * lp,struct place * p2,char * line)332 d_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
397 static
398 void
d_undef(struct lineplace * lp,struct place * p2,char * line)399 d_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
412 static
413 bool
tryinclude(struct place * p,char * line)414 tryinclude(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
438 static
439 void
d_include(struct lineplace * lp,struct place * p2,char * line)440 d_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
467 static
468 void
d_line(struct lineplace * lp,struct place * p2,char * line)469 d_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
524 fail:
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
534 static
535 void
d_warning(struct lineplace * lp,struct place * p2,char * line)536 d_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
548 static
549 void
d_error(struct lineplace * lp,struct place * p2,char * line)550 d_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
563 static
564 void
d_pragma(struct lineplace * lp,struct place * p2,char * line)565 d_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
576 static 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 };
595 static const unsigned numdirectives = HOWMANY(directives);
596
597 static
598 void
directive_gotdirective(struct lineplace * lp,char * line)599 directive_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 */
640 static
641 size_t
directive_scancomments(const struct lineplace * lp,char * line,size_t len)642 directive_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
683 void
directive_gotline(struct lineplace * lp,char * line,size_t len)684 directive_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
706 void
directive_goteof(struct place * p)707 directive_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
721 void
directive_init(void)722 directive_init(void)
723 {
724 ifstate = ifstate_create(NULL, NULL, true);
725 }
726
727 void
directive_cleanup(void)728 directive_cleanup(void)
729 {
730 assert(ifstate->prev == NULL);
731 ifstate_destroy(ifstate);
732 ifstate = NULL;
733 }
734