xref: /openbsd-src/libexec/tradcpp/directive.c (revision c90a81c56dcebd6a1b73fe4aff9b03385b8e63b3)
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
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
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 		p2->column += 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 *
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
148 ifstate_destroy(struct ifstate *is)
149 {
150 	dofree(is, sizeof(*is));
151 }
152 
153 static
154 void
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
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
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
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
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
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
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
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
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 			p2->column += pos;
352 			complain(p2, "Left parenthesis in macro parameters");
353 			complain_fail();
354 			return;
355 		}
356 		if (line[pos] != ')') {
357 			p2->column += 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 	p3.column += argpos;
382 
383 	p4 = *p2;
384 	p4.column += 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
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
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
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
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, "No line number in #line directive");
494 		goto fail;
495 	}
496 #if UINT_MAX < ULONG_MAX
497 	if (val > UINT_MAX) {
498 		complain(&lp->current,
499 			 "Line number in #line directive too large");
500 		goto fail;
501 	}
502 #endif
503 	moretext += strspn(moretext, ws);
504 	moretextlen = strlen(moretext);
505 	lp->current.column += (moretext - text);
506 
507 	if (moretextlen > 2 &&
508 	    moretext[0] == '"' && moretext[moretextlen-1] == '"') {
509 		filename = dostrndup(moretext+1, moretextlen-2);
510 		place_changefile(&lp->nextline, filename);
511 		dostrfree(filename);
512 	}
513 	else if (moretextlen > 0) {
514 		complain(&lp->current,
515 			 "Invalid file name in #line directive");
516 		goto fail;
517 	}
518 
519 	lp->nextline.line = val;
520 	dostrfree(text);
521 	return;
522 
523 fail:
524 	complain(&lp->current, "Before macro expansion: #line %s", line);
525 	complain(&lp->current, "After macro expansion: #line %s", text);
526 	complain_fail();
527 	dostrfree(text);
528 }
529 
530 ////////////////////////////////////////////////////////////
531 // messages
532 
533 static
534 void
535 d_warning(struct lineplace *lp, struct place *p2, char *line)
536 {
537 	char *msg;
538 
539 	msg = macroexpand(p2, line, strlen(line), false);
540 	complain(&lp->current, "#warning: %s", msg);
541 	if (mode.werror) {
542 		complain_fail();
543 	}
544 	dostrfree(msg);
545 }
546 
547 static
548 void
549 d_error(struct lineplace *lp, struct place *p2, char *line)
550 {
551 	char *msg;
552 
553 	msg = macroexpand(p2, line, strlen(line), false);
554 	complain(&lp->current, "#error: %s", msg);
555 	complain_fail();
556 	dostrfree(msg);
557 }
558 
559 ////////////////////////////////////////////////////////////
560 // other
561 
562 static
563 void
564 d_pragma(struct lineplace *lp, struct place *p2, char *line)
565 {
566 	(void)p2;
567 
568 	complain(&lp->current, "#pragma %s", line);
569 	complain_fail();
570 }
571 
572 ////////////////////////////////////////////////////////////
573 // directive table
574 
575 static const struct {
576 	const char *name;
577 	bool ifskip;
578 	void (*func)(struct lineplace *, struct place *, char *line);
579 } directives[] = {
580 	{ "define",  true,  d_define },
581 	{ "elif",    false, d_elif },
582 	{ "else",    false, d_else },
583 	{ "endif",   false, d_endif },
584 	{ "error",   true,  d_error },
585 	{ "if",      false, d_if },
586 	{ "ifdef",   false, d_ifdef },
587 	{ "ifndef",  false, d_ifndef },
588 	{ "include", true,  d_include },
589 	{ "line",    true,  d_line },
590 	{ "pragma",  true,  d_pragma },
591 	{ "undef",   true,  d_undef },
592 	{ "warning", true,  d_warning },
593 };
594 static const unsigned numdirectives = HOWMANY(directives);
595 
596 static
597 void
598 directive_gotdirective(struct lineplace *lp, char *line)
599 {
600 	struct place p2;
601 	size_t len, skip;
602 	unsigned i;
603 
604 	p2 = lp->current;
605 	for (i=0; i<numdirectives; i++) {
606 		len = strlen(directives[i].name);
607 		if (!strncmp(line, directives[i].name, len) &&
608 		    strchr(ws, line[len])) {
609 			if (directives[i].ifskip && !ifstate->curtrue) {
610 				return;
611 			}
612 			skip = len + strspn(line+len, ws);
613 			p2.column += skip;
614 			line += skip;
615 
616 			len = strlen(line);
617 			len = notrailingws(line, len);
618 			if (len < strlen(line)) {
619 				line[len] = '\0';
620 			}
621 			directives[i].func(lp, &p2, line);
622 			return;
623 		}
624 	}
625 	/* ugh. allow # by itself, including with a comment after it */
626 	uncomment(line);
627 	if (line[0] == '\0') {
628 		return;
629 	}
630 
631 	skip = strcspn(line, ws);
632 	complain(&lp->current, "Unknown directive #%.*s", (int)skip, line);
633 	complain_fail();
634 }
635 
636 /*
637  * Check for nested comment delimiters in LINE.
638  */
639 static
640 size_t
641 directive_scancomments(const struct lineplace *lp, char *line, size_t len)
642 {
643 	size_t pos;
644 	bool incomment;
645 	struct place p2;
646 
647 	p2 = lp->current;
648 	incomment = 0;
649 	for (pos = 0; pos+1 < len; pos++) {
650 		if (line[pos] == '/' && line[pos+1] == '*') {
651 			if (incomment) {
652 				complain(&p2, "Warning: %c%c within comment",
653 					 '/', '*');
654 				if (mode.werror) {
655 					complain_failed();
656 				}
657 			} else {
658 				incomment = true;
659 			}
660 			pos++;
661 		} else if (line[pos] == '*' && line[pos+1] == '/') {
662 			if (incomment) {
663 				incomment = false;
664 			} else {
665 				/* stray end-comment; should we care? */
666 			}
667 			pos++;
668 		}
669 		if (line[pos] == '\n') {
670 			p2.line++;
671 			p2.column = 0;
672 		} else {
673 			p2.column++;
674 		}
675 	}
676 
677 	/* multiline comments are supposed to arrive in a single buffer */
678 	assert(!incomment);
679 	return len;
680 }
681 
682 void
683 directive_gotline(struct lineplace *lp, char *line, size_t len)
684 {
685 	size_t skip;
686 
687 	if (warns.nestcomment) {
688 		directive_scancomments(lp, line, len);
689 	}
690 
691 	/* check if we have a directive line (# exactly in column 0) */
692 	if (len > 0 && line[0] == '#') {
693 		skip = 1 + strspn(line + 1, ws);
694 		assert(skip <= len);
695 		lp->current.column += skip;
696 		assert(line[len] == '\0');
697 		directive_gotdirective(lp, line+skip /*, length = len-skip */);
698 		lp->current.column += len-skip;
699 	} else if (ifstate->curtrue) {
700 		macro_sendline(&lp->current, line, len);
701 		lp->current.column += len;
702 	}
703 }
704 
705 void
706 directive_goteof(struct place *p)
707 {
708 	while (ifstate->prev != NULL) {
709 		complain(p, "Missing #endif");
710 		complain(&ifstate->startplace, "...opened at this point");
711 		complain_failed();
712 		ifstate_pop();
713 	}
714 	macro_sendeof(p);
715 }
716 
717 ////////////////////////////////////////////////////////////
718 // module initialization
719 
720 void
721 directive_init(void)
722 {
723 	ifstate = ifstate_create(NULL, NULL, true);
724 }
725 
726 void
727 directive_cleanup(void)
728 {
729 	assert(ifstate->prev == NULL);
730 	ifstate_destroy(ifstate);
731 	ifstate = NULL;
732 }
733