Module Name: src Committed By: rillig Date: Mon Jun 19 12:53:57 UTC 2023
Modified Files: src/usr.bin/make: cond.c make.h parse.c src/usr.bin/make/unit-tests: directive-include-guard.exp directive-include-guard.mk Log Message: make: if a makefile is protected by a guard, only include it once "looks reasonable" sjg@ To generate a diff of this commit: cvs rdiff -u -r1.346 -r1.347 src/usr.bin/make/cond.c cvs rdiff -u -r1.321 -r1.322 src/usr.bin/make/make.h cvs rdiff -u -r1.699 -r1.700 src/usr.bin/make/parse.c cvs rdiff -u -r1.3 -r1.4 \ src/usr.bin/make/unit-tests/directive-include-guard.exp cvs rdiff -u -r1.4 -r1.5 \ src/usr.bin/make/unit-tests/directive-include-guard.mk Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/usr.bin/make/cond.c diff -u src/usr.bin/make/cond.c:1.346 src/usr.bin/make/cond.c:1.347 --- src/usr.bin/make/cond.c:1.346 Fri Jun 16 07:12:46 2023 +++ src/usr.bin/make/cond.c Mon Jun 19 12:53:57 2023 @@ -1,4 +1,4 @@ -/* $NetBSD: cond.c,v 1.346 2023/06/16 07:12:46 rillig Exp $ */ +/* $NetBSD: cond.c,v 1.347 2023/06/19 12:53:57 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -92,7 +92,7 @@ #include "dir.h" /* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: cond.c,v 1.346 2023/06/16 07:12:46 rillig Exp $"); +MAKE_RCSID("$NetBSD: cond.c,v 1.347 2023/06/19 12:53:57 rillig Exp $"); /* * Conditional expressions conform to this grammar: @@ -1123,6 +1123,7 @@ Cond_EvalLine(const char *line) /* Return state for previous conditional */ cond_depth--; + Parse_GuardEndif(); return cond_states[cond_depth] & IFS_ACTIVE ? CR_TRUE : CR_FALSE; } @@ -1150,6 +1151,7 @@ Cond_EvalLine(const char *line) Parse_Error(PARSE_FATAL, "if-less else"); return CR_TRUE; } + Parse_GuardElse(); state = cond_states[cond_depth]; if (state == IFS_INITIAL) { @@ -1185,6 +1187,7 @@ Cond_EvalLine(const char *line) Parse_Error(PARSE_FATAL, "if-less elif"); return CR_TRUE; } + Parse_GuardElse(); state = cond_states[cond_depth]; if (state & IFS_SEEN_ELSE) { Parse_Error(PARSE_WARNING, "extra elif"); @@ -1234,6 +1237,57 @@ Cond_EvalLine(const char *line) return res; } +static bool +skip_identifier(const char **pp) +{ + const char *p = *pp; + + if (ch_isalpha(*p) || *p == '_') { + while (ch_isalnum(*p) || *p == '_') + p++; + *pp = p; + return true; + } + return false; +} + +/* + * Tests whether the line is a conditional that forms a multiple-inclusion + * guard, and if so, extracts the guard variable name. + */ +char * +Cond_ExtractGuard(const char *line) +{ + const char *p = line, *dir, *varname; + size_t dir_len; + + if (!skip_string(&p, ".")) + return NULL; + cpp_skip_hspace(&p); + + dir = p; + while (ch_isalpha(*p)) + p++; + dir_len = (size_t)(p - dir); + cpp_skip_hspace(&p); + + if (dir_len == 2 && memcmp(dir, "if", 2) == 0) { + if (!skip_string(&p, "!defined(")) + return NULL; + varname = p; + skip_identifier(&p); + if (p > varname && strcmp(p, ")") == 0) + return bmake_strsedup(varname, p); + } + if (dir_len == 6 && memcmp(dir, "ifndef", 6) == 0) { + varname = p; + skip_identifier(&p); + if (p > varname && *p == '\0') + return bmake_strsedup(varname, p); + } + return NULL; +} + void Cond_EndFile(void) { Index: src/usr.bin/make/make.h diff -u src/usr.bin/make/make.h:1.321 src/usr.bin/make/make.h:1.322 --- src/usr.bin/make/make.h:1.321 Fri Jun 16 07:12:46 2023 +++ src/usr.bin/make/make.h Mon Jun 19 12:53:57 2023 @@ -1,4 +1,4 @@ -/* $NetBSD: make.h,v 1.321 2023/06/16 07:12:46 rillig Exp $ */ +/* $NetBSD: make.h,v 1.322 2023/06/19 12:53:57 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -793,6 +793,7 @@ void Compat_Make(GNode *, GNode *); extern unsigned int cond_depth; CondResult Cond_EvalCondition(const char *) MAKE_ATTR_USE; CondResult Cond_EvalLine(const char *) MAKE_ATTR_USE; +char *Cond_ExtractGuard(const char *) MAKE_ATTR_USE; void Cond_EndFile(void); /* dir.c; see also dir.h */ @@ -857,6 +858,8 @@ void Parse_PushInput(const char *, unsig void Parse_MainName(GNodeList *); int Parse_NumErrors(void) MAKE_ATTR_USE; unsigned int CurFile_CondMinDepth(void) MAKE_ATTR_USE; +void Parse_GuardElse(void); +void Parse_GuardEndif(void); /* suff.c */ Index: src/usr.bin/make/parse.c diff -u src/usr.bin/make/parse.c:1.699 src/usr.bin/make/parse.c:1.700 --- src/usr.bin/make/parse.c:1.699 Thu Jun 1 06:25:34 2023 +++ src/usr.bin/make/parse.c Mon Jun 19 12:53:57 2023 @@ -1,4 +1,4 @@ -/* $NetBSD: parse.c,v 1.699 2023/06/01 06:25:34 rillig Exp $ */ +/* $NetBSD: parse.c,v 1.700 2023/06/19 12:53:57 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -105,7 +105,15 @@ #include "pathnames.h" /* "@(#)parse.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: parse.c,v 1.699 2023/06/01 06:25:34 rillig Exp $"); +MAKE_RCSID("$NetBSD: parse.c,v 1.700 2023/06/19 12:53:57 rillig Exp $"); + +/* Detects a multiple-inclusion guard in a makefile. */ +typedef enum { + GS_START, /* at the beginning of the file */ + GS_COND, /* after the guard condition */ + GS_DONE, /* after the closing .endif */ + GS_NO /* the file is not guarded */ +} GuardState; /* * A file being read. @@ -128,6 +136,9 @@ typedef struct IncludedFile { char *buf_ptr; /* next char to be read */ char *buf_end; /* buf_end[-1] == '\n' */ + GuardState guard; + char *guardVarname; + struct ForLoop *forLoop; } IncludedFile; @@ -299,6 +310,8 @@ static const struct { enum PosixState posix_state = PS_NOT_YET; +static HashTable guards; + static IncludedFile * GetInclude(size_t i) { @@ -1212,6 +1225,7 @@ IncludeFile(const char *file, bool isSys { Buffer buf; char *fullname; /* full pathname of file */ + char *guardVarname; int fd; fullname = file[0] == '/' ? bmake_strdup(file) : NULL; @@ -1231,6 +1245,14 @@ IncludeFile(const char *file, bool isSys return; } + guardVarname = HashTable_FindValue(&guards, fullname); + if (guardVarname != NULL + && GNode_ValueDirect(SCOPE_GLOBAL, guardVarname) != NULL) { + DEBUG2(PARSE, "Skipping '%s' because '%s' is already set\n", + fullname, guardVarname); + return; + } + if ((fd = open(fullname, O_RDONLY)) == -1) { if (!silent) Parse_Error(PARSE_FATAL, "Cannot open %s", fullname); @@ -2174,6 +2196,8 @@ Parse_PushInput(const char *name, unsign curFile->forBodyReadLines = readLines; curFile->buf = buf; curFile->depending = doing_depend; /* restore this on EOF */ + curFile->guard = forLoop == NULL ? GS_START : GS_NO; + curFile->guardVarname = NULL; curFile->forLoop = forLoop; if (forLoop != NULL && !For_NextIteration(forLoop, &curFile->buf)) @@ -2316,8 +2340,15 @@ ParseEOF(void) Cond_EndFile(); + if (curFile->guard == GS_DONE) { + HashTable_Set(&guards, + curFile->name.str, curFile->guardVarname); + curFile->guardVarname = NULL; + } + FStr_Done(&curFile->name); Buf_Done(&curFile->buf); + free(curFile->guardVarname); if (curFile->forLoop != NULL) ForLoop_Free(curFile->forLoop); Vector_Pop(&includes); @@ -2616,8 +2647,10 @@ static char * ReadHighLevelLine(void) { char *line; + CondResult condResult; for (;;) { + IncludedFile *curFile = CurFile(); line = ReadLowLevelLine(LK_NONEMPTY); if (posix_state == PS_MAYBE_NEXT_LINE) posix_state = PS_NOW_OR_NEVER; @@ -2626,10 +2659,24 @@ ReadHighLevelLine(void) if (line == NULL) return NULL; + if (curFile->guard != GS_NO + && ((curFile->guard == GS_START && line[0] != '.') + || curFile->guard == GS_DONE)) + curFile->guard = GS_NO; if (line[0] != '.') return line; - switch (Cond_EvalLine(line)) { + condResult = Cond_EvalLine(line); + if (curFile->guard == GS_START) { + char *varname; + if (condResult == CR_TRUE + && (varname = Cond_ExtractGuard(line)) != NULL) { + curFile->guard = GS_COND; + curFile->guardVarname = varname; + } else + curFile->guard = GS_NO; + } + switch (condResult) { case CR_FALSE: /* May also mean a syntax error. */ if (!SkipIrrelevantBranches()) return NULL; @@ -2784,6 +2831,22 @@ Parse_VarAssign(const char *line, bool f return true; } +void +Parse_GuardElse(void) +{ + IncludedFile *curFile = CurFile(); + if (cond_depth == curFile->condMinDepth + 1) + curFile->guard = GS_NO; +} + +void +Parse_GuardEndif(void) +{ + IncludedFile *curFile = CurFile(); + if (cond_depth == curFile->condMinDepth && curFile->guard == GS_COND) + curFile->guard = GS_DONE; +} + static char * FindSemicolon(char *p) { @@ -2974,6 +3037,7 @@ Parse_Init(void) sysIncPath = SearchPath_New(); defSysIncPath = SearchPath_New(); Vector_Init(&includes, sizeof(IncludedFile)); + HashTable_Init(&guards); } /* Clean up the parsing module. */ @@ -2981,6 +3045,8 @@ void Parse_End(void) { #ifdef CLEANUP + HashIter hi; + Lst_DoneCall(&targCmds, free); assert(targets == NULL); SearchPath_Free(defSysIncPath); @@ -2988,6 +3054,10 @@ Parse_End(void) SearchPath_Free(parseIncPath); assert(includes.len == 0); Vector_Done(&includes); + HashIter_Init(&hi, &guards); + while (HashIter_Next(&hi) != NULL) + free(hi.entry->value); + HashTable_Done(&guards); #endif } Index: src/usr.bin/make/unit-tests/directive-include-guard.exp diff -u src/usr.bin/make/unit-tests/directive-include-guard.exp:1.3 src/usr.bin/make/unit-tests/directive-include-guard.exp:1.4 --- src/usr.bin/make/unit-tests/directive-include-guard.exp:1.3 Sun Jun 18 20:43:52 2023 +++ src/usr.bin/make/unit-tests/directive-include-guard.exp Mon Jun 19 12:53:57 2023 @@ -1,19 +1,19 @@ Parse_PushInput: file guarded-ifndef.tmp, line 1 -Parse_PushInput: file guarded-ifndef.tmp, line 1 +Skipping 'guarded-ifndef.tmp' because 'GUARDED_IFNDEF' is already set Parse_PushInput: file comments.tmp, line 1 -Parse_PushInput: file comments.tmp, line 1 -Parse_PushInput: file guarded-if.tmp, line 1 +Skipping 'comments.tmp' because 'COMMENTS' is already set Parse_PushInput: file guarded-if.tmp, line 1 +Skipping 'guarded-if.tmp' because 'GUARDED_IF' is already set Parse_PushInput: file triple-negation.tmp, line 1 Parse_PushInput: file triple-negation.tmp, line 1 Parse_PushInput: file varname-mismatch.tmp, line 1 Parse_PushInput: file varname-mismatch.tmp, line 1 Parse_PushInput: file varname-indirect.tmp, line 1 -Parse_PushInput: file varname-indirect.tmp, line 1 +Skipping 'varname-indirect.tmp' because 'VARNAME_INDIRECT' is already set Parse_PushInput: file late-assignment.tmp, line 1 -Parse_PushInput: file late-assignment.tmp, line 1 -Parse_PushInput: file two-conditions.tmp, line 1 +Skipping 'late-assignment.tmp' because 'LATE_ASSIGNMENT' is already set Parse_PushInput: file two-conditions.tmp, line 1 +Skipping 'two-conditions.tmp' because 'TWO_CONDITIONS' is already set Parse_PushInput: file already-set.tmp, line 1 Parse_PushInput: file already-set.tmp, line 1 Parse_PushInput: file twice.tmp, line 1 @@ -31,5 +31,5 @@ Parse_PushInput: file if-elif.tmp, line Parse_PushInput: file if-else.tmp, line 1 Parse_PushInput: file if-else.tmp, line 1 Parse_PushInput: file inner-if-elif-else.tmp, line 1 -Parse_PushInput: file inner-if-elif-else.tmp, line 1 +Skipping 'inner-if-elif-else.tmp' because 'INNER_IF_ELIF_ELSE' is already set exit status 0 Index: src/usr.bin/make/unit-tests/directive-include-guard.mk diff -u src/usr.bin/make/unit-tests/directive-include-guard.mk:1.4 src/usr.bin/make/unit-tests/directive-include-guard.mk:1.5 --- src/usr.bin/make/unit-tests/directive-include-guard.mk:1.4 Sun Jun 18 20:43:52 2023 +++ src/usr.bin/make/unit-tests/directive-include-guard.mk Mon Jun 19 12:53:57 2023 @@ -1,21 +1,20 @@ -# $NetBSD: directive-include-guard.mk,v 1.4 2023/06/18 20:43:52 rillig Exp $ +# $NetBSD: directive-include-guard.mk,v 1.5 2023/06/19 12:53:57 rillig Exp $ # # Tests for multiple-inclusion guards in makefiles. # # A file that is guarded by a multiple-inclusion guard has the following form: # # .ifndef GUARD_NAME -# GUARD_NAME= # any value +# ... +# GUARD_NAME= # any value, may also be empty # ... # .endif # -# When such a file is included for the second time, it has no effect, as all -# its content is skipped. +# When such a file is included later and the guard variable is set, including +# the file has no effect, as all its content is skipped. # # See also: # https://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html -# -# TODO: In these cases, do not include the file, to increase performance. # This is the canonical form of a multiple-inclusion guard. @@ -25,7 +24,7 @@ LINES.guarded-ifndef= \ 'GUARDED_IFNDEF=' \ '.endif' # expect: Parse_PushInput: file guarded-ifndef.tmp, line 1 -# expect: Parse_PushInput: file guarded-ifndef.tmp, line 1 +# expect: Skipping 'guarded-ifndef.tmp' because 'GUARDED_IFNDEF' is already set # Comments and empty lines have no influence on the multiple-inclusion guard. INCS+= comments @@ -38,7 +37,7 @@ LINES.comments= \ '.endif' \ '\# comment' # expect: Parse_PushInput: file comments.tmp, line 1 -# expect: Parse_PushInput: file comments.tmp, line 1 +# expect: Skipping 'comments.tmp' because 'COMMENTS' is already set # An alternative form uses the 'defined' function. It is more verbose than # the canonical form. There are other possible forms as well, such as with a @@ -49,7 +48,7 @@ LINES.guarded-if= \ 'GUARDED_IF=' \ '.endif' # expect: Parse_PushInput: file guarded-if.tmp, line 1 -# expect: Parse_PushInput: file guarded-if.tmp, line 1 +# expect: Skipping 'guarded-if.tmp' because 'GUARDED_IF' is already set # Triple negation is so uncommon that it's not recognized. INCS+= triple-negation @@ -69,23 +68,19 @@ LINES.varname-mismatch= \ # expect: Parse_PushInput: file varname-mismatch.tmp, line 1 # expect: Parse_PushInput: file varname-mismatch.tmp, line 1 -# The variable name in the assignment must only contain alphanumeric -# characters and underscores, in particular, it must not be a dynamically -# computed name. +# The variable name in the guard condition must only contain alphanumeric +# characters and underscores. The guard variable is more flexible, it can be +# set anywhere, as long as it is set when the guarded file is included next. INCS+= varname-indirect LINES.varname-indirect= \ '.ifndef VARNAME_INDIRECT' \ 'VARNAME_$${:UINDIRECT}=' \ '.endif' # expect: Parse_PushInput: file varname-indirect.tmp, line 1 -# expect: Parse_PushInput: file varname-indirect.tmp, line 1 +# expect: Skipping 'varname-indirect.tmp' because 'VARNAME_INDIRECT' is already set -# The variable assignment for the guard must directly follow the conditional. -# -# This requirement may be dropped entirely later, as the guard variable could -# also be undefined while reading the file or at a later point, and as long as -# the implementation checks the guard variable before skipping the file, the -# optimization is still valid. +# The time at which the guard variable is set doesn't matter, as long as it is +# set when the file is included the next time. INCS+= late-assignment LINES.late-assignment= \ '.ifndef LATE_ASSIGNMENT' \ @@ -93,10 +88,10 @@ LINES.late-assignment= \ 'LATE_ASSIGNMENT=' \ '.endif' # expect: Parse_PushInput: file late-assignment.tmp, line 1 -# expect: Parse_PushInput: file late-assignment.tmp, line 1 +# expect: Skipping 'late-assignment.tmp' because 'LATE_ASSIGNMENT' is already set -# There must be no other condition between the guard condition and the -# variable assignment. +# The time at which the guard variable is set doesn't matter, as long as it is +# set when the file is included the next time. INCS+= two-conditions LINES.two-conditions= \ '.ifndef TWO_CONDITIONS' \ @@ -105,11 +100,11 @@ LINES.two-conditions= \ '. endif' \ '.endif' # expect: Parse_PushInput: file two-conditions.tmp, line 1 -# expect: Parse_PushInput: file two-conditions.tmp, line 1 +# expect: Skipping 'two-conditions.tmp' because 'TWO_CONDITIONS' is already set # If the guard variable is already set before the file is included for the -# first time, that file is not considered to be guarded. It's a situation -# that is uncommon in practice. +# first time, the file is not considered guarded, as the makefile parser skips +# all lines in the inactive part between the '.ifndef' and the '.endif'. INCS+= already-set LINES.already-set= \ '.ifndef ALREADY_SET' \ @@ -123,18 +118,20 @@ ALREADY_SET= # several, even if they have the same effect. INCS+= twice LINES.twice= \ - '.ifndef TWICE' \ - 'TWICE=' \ + '.ifndef TWICE_FIRST' \ + 'TWICE_FIRST=' \ '.endif' \ - '.ifndef TWICE' \ - 'TWICE=' \ + '.ifndef TWICE_SECOND' \ + 'TWICE_SECOND=' \ '.endif' # expect: Parse_PushInput: file twice.tmp, line 1 # expect: Parse_PushInput: file twice.tmp, line 1 # When multiple files use the same guard variable name, they exclude each -# other. It's the responsibility of the makefile authors to choose suitable -# variable names. Typical choices are ${PROJECT}_${DIR}_${FILE}_MK. +# other. It's the responsibility of the makefile authors to choose unique +# variable names. Typical choices are ${PROJECT}_${DIR}_${FILE}_MK. This is +# the same situation as in the 'already-set' test, and the file is not +# considered guarded. INCS+= reuse LINES.reuse= \ ${LINES.guarded-if} @@ -211,7 +208,8 @@ LINES.inner-if-elif-else = \ '. endif' \ '.endif' # expect: Parse_PushInput: file inner-if-elif-else.tmp, line 1 -# expect: Parse_PushInput: file inner-if-elif-else.tmp, line 1 +# expect: Skipping 'inner-if-elif-else.tmp' because 'INNER_IF_ELIF_ELSE' is already set + # Include each of the files twice. The directive-include-guard.exp file # contains a single entry for the files whose multiple-inclusion guard works,