xref: /openbsd/libexec/tradcpp/directive.c (revision 88157d21)
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