Home | History | Annotate | Line # | Download | only in dist
      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 		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 *
    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 			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
    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,
    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
    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
    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
    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
    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
    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
    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
    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
    722 directive_init(void)
    723 {
    724 	ifstate = ifstate_create(NULL, NULL, true);
    725 }
    726 
    727 void
    728 directive_cleanup(void)
    729 {
    730 	assert(ifstate->prev == NULL);
    731 	ifstate_destroy(ifstate);
    732 	ifstate = NULL;
    733 }
    734