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