734 lines
15 KiB
C
734 lines
15 KiB
C
/*-
|
|
* Copyright (c) 2010, 2013 The NetBSD Foundation, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
* by David A. Holland.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include <errno.h>
|
|
|
|
#include "bool.h"
|
|
#include "utils.h"
|
|
#include "mode.h"
|
|
#include "place.h"
|
|
#include "files.h"
|
|
#include "directive.h"
|
|
#include "macro.h"
|
|
#include "eval.h"
|
|
#include "output.h"
|
|
|
|
struct ifstate {
|
|
struct ifstate *prev;
|
|
struct place startplace;
|
|
bool curtrue;
|
|
bool evertrue;
|
|
bool seenelse;
|
|
};
|
|
|
|
static struct ifstate *ifstate;
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// common parsing bits
|
|
|
|
static
|
|
void
|
|
uncomment(char *buf)
|
|
{
|
|
char *s, *t, *u = NULL;
|
|
bool incomment = false;
|
|
bool inesc = false;
|
|
bool inquote = false;
|
|
char quote = '\0';
|
|
|
|
for (s = t = buf; *s; s++) {
|
|
if (incomment) {
|
|
if (s[0] == '*' && s[1] == '/') {
|
|
s++;
|
|
incomment = false;
|
|
}
|
|
} else {
|
|
if (!inquote && s[0] == '/' && s[1] == '*') {
|
|
incomment = true;
|
|
} else {
|
|
if (inesc) {
|
|
inesc = false;
|
|
} else if (s[0] == '\\') {
|
|
inesc = true;
|
|
} else if (!inquote &&
|
|
(s[0] == '"' || s[0] == '\'')) {
|
|
inquote = true;
|
|
quote = s[0];
|
|
} else if (inquote && s[0] == quote) {
|
|
inquote = false;
|
|
}
|
|
|
|
if (t != s) {
|
|
*t = *s;
|
|
}
|
|
if (!strchr(ws, *t)) {
|
|
u = t;
|
|
}
|
|
t++;
|
|
}
|
|
}
|
|
}
|
|
if (u) {
|
|
/* end string after last non-whitespace char */
|
|
u[1] = '\0';
|
|
} else {
|
|
*t = '\0';
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
oneword(const char *what, struct place *p2, char *line)
|
|
{
|
|
size_t pos;
|
|
|
|
pos = strcspn(line, ws);
|
|
if (line[pos] != '\0') {
|
|
place_addcolumns(p2, pos);
|
|
complain(p2, "Garbage after %s argument", what);
|
|
complain_fail();
|
|
line[pos] = '\0';
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// if handling
|
|
|
|
static
|
|
struct ifstate *
|
|
ifstate_create(struct ifstate *prev, struct place *p, bool startstate)
|
|
{
|
|
struct ifstate *is;
|
|
|
|
is = domalloc(sizeof(*is));
|
|
is->prev = prev;
|
|
if (p != NULL) {
|
|
is->startplace = *p;
|
|
} else {
|
|
place_setbuiltin(&is->startplace, 1);
|
|
}
|
|
is->curtrue = startstate;
|
|
is->evertrue = is->curtrue;
|
|
is->seenelse = false;
|
|
return is;
|
|
}
|
|
|
|
static
|
|
void
|
|
ifstate_destroy(struct ifstate *is)
|
|
{
|
|
dofree(is, sizeof(*is));
|
|
}
|
|
|
|
static
|
|
void
|
|
ifstate_push(struct place *p, bool startstate)
|
|
{
|
|
struct ifstate *newstate;
|
|
|
|
newstate = ifstate_create(ifstate, p, startstate);
|
|
if (!ifstate->curtrue) {
|
|
newstate->curtrue = false;
|
|
newstate->evertrue = true;
|
|
}
|
|
ifstate = newstate;
|
|
}
|
|
|
|
static
|
|
void
|
|
ifstate_pop(void)
|
|
{
|
|
struct ifstate *is;
|
|
|
|
is = ifstate;
|
|
ifstate = ifstate->prev;
|
|
ifstate_destroy(is);
|
|
}
|
|
|
|
static
|
|
void
|
|
d_if(struct lineplace *lp, struct place *p2, char *line)
|
|
{
|
|
bool doprint;
|
|
char *expr;
|
|
bool val;
|
|
struct place p3 = *p2;
|
|
size_t oldlen;
|
|
|
|
doprint = ifstate->curtrue;
|
|
|
|
expr = macroexpand(p2, line, strlen(line), true);
|
|
|
|
oldlen = strlen(expr);
|
|
uncomment(expr);
|
|
/* trim to fit, so the malloc debugging won't complain */
|
|
expr = dorealloc(expr, oldlen + 1, strlen(expr) + 1);
|
|
|
|
if (ifstate->curtrue) {
|
|
val = eval(&p3, expr);
|
|
} else {
|
|
val = 0;
|
|
}
|
|
ifstate_push(&lp->current, val);
|
|
dostrfree(expr);
|
|
|
|
if (doprint) {
|
|
debuglog(&lp->current, "#if: %s",
|
|
ifstate->curtrue ? "taken" : "not taken");
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
d_ifdef(struct lineplace *lp, struct place *p2, char *line)
|
|
{
|
|
bool doprint;
|
|
|
|
doprint = ifstate->curtrue;
|
|
|
|
uncomment(line);
|
|
oneword("#ifdef", p2, line);
|
|
ifstate_push(&lp->current, macro_isdefined(line));
|
|
|
|
if (doprint) {
|
|
debuglog(&lp->current, "#ifdef %s: %s",
|
|
line, ifstate->curtrue ? "taken" : "not taken");
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
d_ifndef(struct lineplace *lp, struct place *p2, char *line)
|
|
{
|
|
bool doprint;
|
|
|
|
doprint = ifstate->curtrue;
|
|
|
|
uncomment(line);
|
|
oneword("#ifndef", p2, line);
|
|
ifstate_push(&lp->current, !macro_isdefined(line));
|
|
|
|
if (doprint) {
|
|
debuglog(&lp->current, "#ifndef %s: %s",
|
|
line, ifstate->curtrue ? "taken" : "not taken");
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
d_elif(struct lineplace *lp, struct place *p2, char *line)
|
|
{
|
|
bool doprint;
|
|
char *expr;
|
|
struct place p3 = *p2;
|
|
size_t oldlen;
|
|
|
|
if (ifstate->seenelse) {
|
|
complain(&lp->current, "#elif after #else");
|
|
complain_fail();
|
|
}
|
|
|
|
doprint = ifstate->curtrue;
|
|
|
|
if (ifstate->evertrue) {
|
|
ifstate->curtrue = false;
|
|
} else {
|
|
expr = macroexpand(p2, line, strlen(line), true);
|
|
|
|
oldlen = strlen(expr);
|
|
uncomment(expr);
|
|
/* trim to fit, so the malloc debugging won't complain */
|
|
expr = dorealloc(expr, oldlen + 1, strlen(expr) + 1);
|
|
|
|
ifstate->curtrue = eval(&p3, expr);
|
|
ifstate->evertrue = ifstate->curtrue;
|
|
dostrfree(expr);
|
|
}
|
|
|
|
if (doprint) {
|
|
debuglog2(&lp->current, &ifstate->startplace, "#elif: %s",
|
|
ifstate->curtrue ? "taken" : "not taken");
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
d_else(struct lineplace *lp, struct place *p2, char *line)
|
|
{
|
|
bool doprint;
|
|
|
|
(void)p2;
|
|
(void)line;
|
|
|
|
if (ifstate->seenelse) {
|
|
complain(&lp->current,
|
|
"Multiple #else directives in one conditional");
|
|
complain_fail();
|
|
}
|
|
|
|
doprint = ifstate->curtrue;
|
|
|
|
ifstate->curtrue = !ifstate->evertrue;
|
|
ifstate->evertrue = true;
|
|
ifstate->seenelse = true;
|
|
|
|
if (doprint) {
|
|
debuglog2(&lp->current, &ifstate->startplace, "#else: %s",
|
|
ifstate->curtrue ? "taken" : "not taken");
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
d_endif(struct lineplace *lp, struct place *p2, char *line)
|
|
{
|
|
(void)p2;
|
|
(void)line;
|
|
|
|
if (ifstate->prev == NULL) {
|
|
complain(&lp->current, "Unmatched #endif");
|
|
complain_fail();
|
|
} else {
|
|
debuglog2(&lp->current, &ifstate->startplace, "#endif");
|
|
ifstate_pop();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// macros
|
|
|
|
static
|
|
void
|
|
d_define(struct lineplace *lp, struct place *p2, char *line)
|
|
{
|
|
size_t pos, argpos;
|
|
struct place p3, p4;
|
|
|
|
(void)lp;
|
|
|
|
/*
|
|
* line may be:
|
|
* macro expansion
|
|
* macro(arg, arg, ...) expansion
|
|
*/
|
|
|
|
pos = strcspn(line, " \t\f\v(");
|
|
if (line[pos] == '(') {
|
|
line[pos++] = '\0';
|
|
argpos = pos;
|
|
pos = pos + strcspn(line+pos, "()");
|
|
if (line[pos] == '(') {
|
|
place_addcolumns(p2, pos);
|
|
complain(p2, "Left parenthesis in macro parameters");
|
|
complain_fail();
|
|
return;
|
|
}
|
|
if (line[pos] != ')') {
|
|
place_addcolumns(p2, pos);
|
|
complain(p2, "Unclosed macro parameter list");
|
|
complain_fail();
|
|
return;
|
|
}
|
|
line[pos++] = '\0';
|
|
#if 0
|
|
if (!strchr(ws, line[pos])) {
|
|
p2->column += pos;
|
|
complain(p2, "Trash after macro parameter list");
|
|
complain_fail();
|
|
return;
|
|
}
|
|
#endif
|
|
} else if (line[pos] == '\0') {
|
|
argpos = 0;
|
|
} else {
|
|
line[pos++] = '\0';
|
|
argpos = 0;
|
|
}
|
|
|
|
pos += strspn(line+pos, ws);
|
|
|
|
p3 = *p2;
|
|
place_addcolumns(&p3, argpos);
|
|
|
|
p4 = *p2;
|
|
place_addcolumns(&p4, pos);
|
|
|
|
if (argpos) {
|
|
debuglog(&lp->current, "Defining %s()", line);
|
|
macro_define_params(p2, line, &p3,
|
|
line + argpos, &p4,
|
|
line + pos);
|
|
} else {
|
|
debuglog(&lp->current, "Defining %s", line);
|
|
macro_define_plain(p2, line, &p4, line + pos);
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
d_undef(struct lineplace *lp, struct place *p2, char *line)
|
|
{
|
|
(void)lp;
|
|
|
|
uncomment(line);
|
|
oneword("#undef", p2, line);
|
|
debuglog(&lp->current, "Undef %s", line);
|
|
macro_undef(line);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// includes
|
|
|
|
static
|
|
bool
|
|
tryinclude(struct place *p, char *line)
|
|
{
|
|
size_t len;
|
|
|
|
len = strlen(line);
|
|
if (len > 2 && line[0] == '"' && line[len-1] == '"') {
|
|
line[len-1] = '\0';
|
|
debuglog(p, "Entering include file \"%s\"", line+1);
|
|
file_readquote(p, line+1);
|
|
debuglog(p, "Leaving include file \"%s\"", line+1);
|
|
line[len-1] = '"';
|
|
return true;
|
|
}
|
|
if (len > 2 && line[0] == '<' && line[len-1] == '>') {
|
|
line[len-1] = '\0';
|
|
debuglog(p, "Entering include file <%s>", line+1);
|
|
file_readbracket(p, line+1);
|
|
debuglog(p, "Leaving include file <%s>", line+1);
|
|
line[len-1] = '>';
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static
|
|
void
|
|
d_include(struct lineplace *lp, struct place *p2, char *line)
|
|
{
|
|
char *text;
|
|
size_t oldlen;
|
|
|
|
uncomment(line);
|
|
if (tryinclude(&lp->current, line)) {
|
|
return;
|
|
}
|
|
text = macroexpand(p2, line, strlen(line), false);
|
|
|
|
oldlen = strlen(text);
|
|
uncomment(text);
|
|
/* trim to fit, so the malloc debugging won't complain */
|
|
text = dorealloc(text, oldlen + 1, strlen(text) + 1);
|
|
|
|
if (tryinclude(&lp->current, text)) {
|
|
dostrfree(text);
|
|
return;
|
|
}
|
|
complain(&lp->current, "Illegal #include directive");
|
|
complain(&lp->current, "Before macro expansion: #include %s", line);
|
|
complain(&lp->current, "After macro expansion: #include %s", text);
|
|
dostrfree(text);
|
|
complain_fail();
|
|
}
|
|
|
|
static
|
|
void
|
|
d_line(struct lineplace *lp, struct place *p2, char *line)
|
|
{
|
|
char *text;
|
|
size_t oldlen;
|
|
unsigned long val;
|
|
char *moretext;
|
|
size_t moretextlen;
|
|
char *filename;
|
|
|
|
text = macroexpand(p2, line, strlen(line), true);
|
|
|
|
oldlen = strlen(text);
|
|
uncomment(text);
|
|
/* trim to fit, so the malloc debugging won't complain */
|
|
text = dorealloc(text, oldlen + 1, strlen(text) + 1);
|
|
|
|
/*
|
|
* What we should have here: either 1234 "file.c",
|
|
* or just 1234.
|
|
*/
|
|
|
|
errno = 0;
|
|
val = strtoul(text, &moretext, 10);
|
|
if (errno) {
|
|
complain(&lp->current,
|
|
"Invalid line number in #line directive");
|
|
goto fail;
|
|
}
|
|
#if UINT_MAX < ULONG_MAX
|
|
if (val > UINT_MAX) {
|
|
complain(&lp->current,
|
|
"Line number in #line directive too large");
|
|
goto fail;
|
|
}
|
|
#endif
|
|
moretext += strspn(moretext, ws);
|
|
moretextlen = strlen(moretext);
|
|
place_addcolumns(&lp->current, moretext - text);
|
|
|
|
if (moretextlen > 2 &&
|
|
moretext[0] == '"' && moretext[moretextlen-1] == '"') {
|
|
filename = dostrndup(moretext+1, moretextlen-2);
|
|
place_changefile(&lp->nextline, filename);
|
|
dostrfree(filename);
|
|
}
|
|
else if (moretextlen > 0) {
|
|
complain(&lp->current,
|
|
"Invalid file name in #line directive");
|
|
goto fail;
|
|
}
|
|
|
|
lp->nextline.line = val;
|
|
dostrfree(text);
|
|
return;
|
|
|
|
fail:
|
|
complain(&lp->current, "Before macro expansion: #line %s", line);
|
|
complain(&lp->current, "After macro expansion: #line %s", text);
|
|
complain_fail();
|
|
dostrfree(text);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// messages
|
|
|
|
static
|
|
void
|
|
d_warning(struct lineplace *lp, struct place *p2, char *line)
|
|
{
|
|
char *msg;
|
|
|
|
msg = macroexpand(p2, line, strlen(line), false);
|
|
complain(&lp->current, "#warning: %s", msg);
|
|
if (mode.werror) {
|
|
complain_fail();
|
|
}
|
|
dostrfree(msg);
|
|
}
|
|
|
|
static
|
|
void
|
|
d_error(struct lineplace *lp, struct place *p2, char *line)
|
|
{
|
|
char *msg;
|
|
|
|
msg = macroexpand(p2, line, strlen(line), false);
|
|
complain(&lp->current, "#error: %s", msg);
|
|
complain_fail();
|
|
dostrfree(msg);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// other
|
|
|
|
static
|
|
void
|
|
d_pragma(struct lineplace *lp, struct place *p2, char *line)
|
|
{
|
|
(void)p2;
|
|
|
|
complain(&lp->current, "#pragma %s", line);
|
|
complain_fail();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// directive table
|
|
|
|
static const struct {
|
|
const char *name;
|
|
bool ifskip;
|
|
void (*func)(struct lineplace *, struct place *, char *line);
|
|
} directives[] = {
|
|
{ "define", true, d_define },
|
|
{ "elif", false, d_elif },
|
|
{ "else", false, d_else },
|
|
{ "endif", false, d_endif },
|
|
{ "error", true, d_error },
|
|
{ "if", false, d_if },
|
|
{ "ifdef", false, d_ifdef },
|
|
{ "ifndef", false, d_ifndef },
|
|
{ "include", true, d_include },
|
|
{ "line", true, d_line },
|
|
{ "pragma", true, d_pragma },
|
|
{ "undef", true, d_undef },
|
|
{ "warning", true, d_warning },
|
|
};
|
|
static const unsigned numdirectives = HOWMANY(directives);
|
|
|
|
static
|
|
void
|
|
directive_gotdirective(struct lineplace *lp, char *line)
|
|
{
|
|
struct place p2;
|
|
size_t len, skip;
|
|
unsigned i;
|
|
|
|
p2 = lp->current;
|
|
for (i=0; i<numdirectives; i++) {
|
|
len = strlen(directives[i].name);
|
|
if (!strncmp(line, directives[i].name, len) &&
|
|
strchr(ws, line[len])) {
|
|
if (directives[i].ifskip && !ifstate->curtrue) {
|
|
return;
|
|
}
|
|
skip = len + strspn(line+len, ws);
|
|
place_addcolumns(&p2, skip);
|
|
line += skip;
|
|
|
|
len = strlen(line);
|
|
len = notrailingws(line, len);
|
|
if (len < strlen(line)) {
|
|
line[len] = '\0';
|
|
}
|
|
directives[i].func(lp, &p2, line);
|
|
return;
|
|
}
|
|
}
|
|
/* ugh. allow # by itself, including with a comment after it */
|
|
uncomment(line);
|
|
if (line[0] == '\0') {
|
|
return;
|
|
}
|
|
|
|
skip = strcspn(line, ws);
|
|
complain(&lp->current, "Unknown directive #%.*s", (int)skip, line);
|
|
complain_fail();
|
|
}
|
|
|
|
/*
|
|
* Check for nested comment delimiters in LINE.
|
|
*/
|
|
static
|
|
size_t
|
|
directive_scancomments(const struct lineplace *lp, char *line, size_t len)
|
|
{
|
|
size_t pos;
|
|
bool incomment;
|
|
struct place p2;
|
|
|
|
p2 = lp->current;
|
|
incomment = 0;
|
|
for (pos = 0; pos+1 < len; pos++) {
|
|
if (line[pos] == '/' && line[pos+1] == '*') {
|
|
if (incomment) {
|
|
complain(&p2, "Warning: %c%c within comment",
|
|
'/', '*');
|
|
if (mode.werror) {
|
|
complain_failed();
|
|
}
|
|
} else {
|
|
incomment = true;
|
|
}
|
|
pos++;
|
|
} else if (line[pos] == '*' && line[pos+1] == '/') {
|
|
if (incomment) {
|
|
incomment = false;
|
|
} else {
|
|
/* stray end-comment; should we care? */
|
|
}
|
|
pos++;
|
|
}
|
|
if (line[pos] == '\n') {
|
|
place_addlines(&p2, 1);
|
|
p2.column = 0;
|
|
} else {
|
|
place_addcolumns(&p2, 1);
|
|
}
|
|
}
|
|
|
|
/* multiline comments are supposed to arrive in a single buffer */
|
|
assert(!incomment);
|
|
return len;
|
|
}
|
|
|
|
void
|
|
directive_gotline(struct lineplace *lp, char *line, size_t len)
|
|
{
|
|
size_t skip;
|
|
|
|
if (warns.nestcomment) {
|
|
directive_scancomments(lp, line, len);
|
|
}
|
|
|
|
/* check if we have a directive line (# exactly in column 0) */
|
|
if (len > 0 && line[0] == '#') {
|
|
skip = 1 + strspn(line + 1, ws);
|
|
assert(skip <= len);
|
|
place_addcolumns(&lp->current, skip);
|
|
assert(line[len] == '\0');
|
|
directive_gotdirective(lp, line+skip /*, length = len-skip */);
|
|
place_addcolumns(&lp->current, len-skip);
|
|
} else if (ifstate->curtrue) {
|
|
macro_sendline(&lp->current, line, len);
|
|
place_addcolumns(&lp->current, len);
|
|
}
|
|
}
|
|
|
|
void
|
|
directive_goteof(struct place *p)
|
|
{
|
|
while (ifstate->prev != NULL) {
|
|
complain(p, "Missing #endif");
|
|
complain(&ifstate->startplace, "...opened at this point");
|
|
complain_failed();
|
|
ifstate_pop();
|
|
}
|
|
macro_sendeof(p);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// module initialization
|
|
|
|
void
|
|
directive_init(void)
|
|
{
|
|
ifstate = ifstate_create(NULL, NULL, true);
|
|
}
|
|
|
|
void
|
|
directive_cleanup(void)
|
|
{
|
|
assert(ifstate->prev == NULL);
|
|
ifstate_destroy(ifstate);
|
|
ifstate = NULL;
|
|
}
|