directive.c revision 1.2
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		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
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			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
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, "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
523fail:
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
533static
534void
535d_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
547static
548void
549d_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
562static
563void
564d_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
575static 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};
594static const unsigned numdirectives = HOWMANY(directives);
595
596static
597void
598directive_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 */
639static
640size_t
641directive_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
682void
683directive_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
705void
706directive_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
720void
721directive_init(void)
722{
723	ifstate = ifstate_create(NULL, NULL, true);
724}
725
726void
727directive_cleanup(void)
728{
729	assert(ifstate->prev == NULL);
730	ifstate_destroy(ifstate);
731	ifstate = NULL;
732}
733