From d6b76411684d9cc7a676e98bd9f39daecfe8af36 Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Mon, 23 Oct 2023 16:17:12 +0100 Subject: make: fix detection of target rules The presence of an equal sign in an inline command on a target rule caused the line to be detected as a macro assignment. For example: target:; @echo a = $(a) Rearrange input parsing so target rules are detected before macro assignments. This is made more complex by having to allow for the ':=', '::=' and ':::=' assignment operators. (And for targets containing colons on Windows.) Costs 240-248 bytes. --- miscutils/make.c | 335 ++++++++++++++++++++++++++++----------------------- testsuite/make.tests | 8 ++ 2 files changed, 191 insertions(+), 152 deletions(-) diff --git a/miscutils/make.c b/miscutils/make.c index b6103bb58..695f77fa6 100644 --- a/miscutils/make.c +++ b/miscutils/make.c @@ -438,8 +438,13 @@ check_name(const char *name) { const char *s; - if (!posix) + if (!posix) { + for (s = name; *s; ++s) { + if (*s == '=') + return FALSE; + } return TRUE; + } for (s = name; *s; ++s) { #if ENABLE_PLATFORM_MINGW32 @@ -1157,44 +1162,55 @@ modify_words(const char *val, int modifier, size_t lenf, size_t lenr, } /* - * Return a pointer to the next instance of a given character. Macro - * expansions are skipped so the ':' and '=' in $(VAR:.s1=.s2) aren't - * detected as separators for rules or macro definitions. + * Try to detect a target rule by searching for a colon that isn't part + * of a macro assignment. Macros must have been expanded already. Return + * a pointer to the colon or NULL. */ static char * -find_char(const char *str, int c) +find_colon(char *p) { - const char *s; + char *q; - for (s = skip_macro(str); *s; s = skip_macro(s + 1)) { - if (*s == c) - return (char *)s; +#if ENABLE_PLATFORM_MINGW32 + for (q = p; (q = strchr(q, ':')); ++q) { + if (posix && !(pragma & P_WINDOWS)) + break; + if (q[1] != '/') + break; + } + if (q != NULL) { +#else + if ((q = strchr(p, ':')) != NULL) { +#endif + // Skip ':=', '::=' and ':::=' macro assignments + if ( + // '::=' and ':::=' are from POSIX 202X. + !(!POSIX_2017 && q[1] == ':' && q[2] == ':' && q[3] == '=') && + !(!POSIX_2017 && q[1] == ':' && q[2] == '=') && + // ':=' is a non-POSIX extension + !(!posix && q[1] == '=') + ) + return q; } return NULL; } -#if ENABLE_PLATFORM_MINGW32 /* - * Ignore colons in targets of the form c:/path when looking for a - * target rule. + * Return a pointer to the next instance of a given character. Macro + * expansions are skipped so the ':' and '=' in $(VAR:.s1=.s2) aren't + * detected as separators for rules or macro definitions. */ static char * -find_colon(const char *str) +find_char(const char *str, int c) { - const char *s = str; + const char *s; - while ((s = find_char(s, ':'))) { - if (posix && !(pragma & P_WINDOWS)) - break; - if (s[1] != '/') - break; - ++s; + for (s = skip_macro(str); *s; s = skip_macro(s + 1)) { + if (*s == c) + return (char *)s; } - return (char *)s; + return NULL; } -#else -# define find_colon(s) find_char(s, ':') -#endif /* * Recursively expand any macros in str to an allocated string. @@ -1881,7 +1897,147 @@ input(FILE *fd, int ilevel) goto end_loop; } - // Check for a macro definition + // Check for target rule + a = p = expanded = expand_macros(str, FALSE); + if ((q = find_colon(p)) != NULL) { + // All tokens before ':' must be valid targets + *q = '\0'; + while ((a = gettok(&p)) != NULL && is_valid_target(a)) + ; + } + free(expanded); + + if (a == NULL) { + // Looks like a target rule + p = expanded = expand_macros(str, FALSE); + + // Look for colon separator + q = find_colon(p); + if (q == NULL) + error("expected separator"); + + *q++ = '\0'; // Separate targets and prerequisites + + // Double colon + dbl = !posix && *q == ':'; + if (dbl) + q++; + + // Look for semicolon separator + cp = NULL; + s = strchr(q, ';'); + if (s) { + *s = '\0'; + // Retrieve command from copy of line + if ((p = find_char(copy, ':')) && (p = strchr(p, ';'))) + newcmd(&cp, process_command(p + 1)); + } + semicolon_cmd = cp != NULL; + + // Create list of prerequisites + dp = NULL; + while (((p = gettok(&q)) != NULL)) { + char *newp = NULL; + + if (!posix) { + // Allow prerequisites of form library(member1 member2). + // Leading and trailing spaces in the brackets are skipped. + if (!lib) { + s = strchr(p, '('); + if (s && !ends_with_bracket(s) && strchr(q, ')')) { + // Looks like an unterminated archive member + // with a terminator later on the line. + lib = p; + if (s[1] != '\0') { + p = newp = auto_concat(lib, ")"); + s[1] = '\0'; + } else { + continue; + } + } + } else if (ends_with_bracket(p)) { + if (*p != ')') + p = newp = auto_concat(lib, p); + lib = NULL; + if (newp == NULL) + continue; + } else { + p = newp = auto_string(xasprintf("%s%s)", lib, p)); + } + } + + // If not in POSIX mode expand wildcards in the name. + nfile = 1; + files = &p; + if (!posix && wildcard(p, &gd)) { + nfile = gd.gl_pathc; + files = gd.gl_pathv; + } + for (i = 0; i < nfile; ++i) { + if (!POSIX_2017 && strcmp(files[i], ".WAIT") == 0) + continue; + np = newname(files[i]); + newdep(&dp, np); + } + if (files != &p) + globfree(&gd); + free(newp); + } + lib = NULL; + + // Create list of commands + startno = dispno; + while ((str2 = readline(fd)) && *str2 == '\t') { + newcmd(&cp, process_command(str2)); + free(str2); + } + dispno = startno; + + // Create target names and attach rule to them + q = expanded; + count = 0; + seen_inference = FALSE; + while ((p = gettok(&q)) != NULL) { + // If not in POSIX mode expand wildcards in the name. + nfile = 1; + files = &p; + if (!posix && wildcard(p, &gd)) { + nfile = gd.gl_pathc; + files = gd.gl_pathv; + } + for (i = 0; i < nfile; ++i) { + int ttype = target_type(files[i]); + + np = newname(files[i]); + if (ttype != T_NORMAL) { + if (ttype == T_INFERENCE && posix) { + if (semicolon_cmd) + error_in_inference_rule("'; command'"); + seen_inference = TRUE; + } + np->n_flag |= N_SPECIAL; + } else if (!firstname) { + firstname = np; + } + addrule(np, dp, cp, dbl); + count++; + } + if (files != &p) + globfree(&gd); + } + if (seen_inference && count != 1) + error_in_inference_rule("multiple targets"); + + // Prerequisites and commands will be unused if there were + // no targets. Avoid leaking memory. + if (count == 0) { + freedeps(dp); + freecmds(cp); + } + goto end_loop; + } + + // If we get here it must be a macro definition q = find_char(str, '='); if (q != NULL) { int level = (useenv || fd == NULL) ? 4 : 3; @@ -1966,135 +2122,10 @@ input(FILE *fd, int ilevel) } setmacro(a, q, level); free(newq); - goto end_loop; - } - - // If we get here it must be a target rule - p = expanded = expand_macros(str, FALSE); - - // Look for colon separator - q = find_colon(p); - if (q == NULL) - error("expected separator"); - - *q++ = '\0'; // Separate targets and prerequisites - - // Double colon - dbl = !posix && *q == ':'; - if (dbl) - q++; - - // Look for semicolon separator - cp = NULL; - s = strchr(q, ';'); - if (s) { - *s = '\0'; - // Retrieve command from copy of line - if ((p = find_colon(copy)) && (p = strchr(p, ';'))) - newcmd(&cp, process_command(p + 1)); - } - semicolon_cmd = cp != NULL; - - // Create list of prerequisites - dp = NULL; - while (((p = gettok(&q)) != NULL)) { - char *newp = NULL; - - if (!posix) { - // Allow prerequisites of form library(member1 member2). - // Leading and trailing spaces in the brackets are skipped. - if (!lib) { - s = strchr(p, '('); - if (s && !ends_with_bracket(s) && strchr(q, ')')) { - // Looks like an unterminated archive member - // with a terminator later on the line. - lib = p; - if (s[1] != '\0') { - p = newp = auto_concat(lib, ")"); - s[1] = '\0'; - } else { - continue; - } - } - } else if (ends_with_bracket(p)) { - if (*p != ')') - p = newp = auto_concat(lib, p); - lib = NULL; - if (newp == NULL) - continue; - } else { - p = newp = auto_string(xasprintf("%s%s)", lib, p)); - } - } - - // If not in POSIX mode expand wildcards in the name. - nfile = 1; - files = &p; - if (!posix && wildcard(p, &gd)) { - nfile = gd.gl_pathc; - files = gd.gl_pathv; - } - for (i = 0; i < nfile; ++i) { - if (!POSIX_2017 && strcmp(files[i], ".WAIT") == 0) - continue; - np = newname(files[i]); - newdep(&dp, np); - } - if (files != &p) - globfree(&gd); - free(newp); + } else { + error("expected macro definition"); } - lib = NULL; - // Create list of commands - startno = dispno; - while ((str2 = readline(fd)) && *str2 == '\t') { - newcmd(&cp, process_command(str2)); - free(str2); - } - dispno = startno; - - // Create target names and attach rule to them - q = expanded; - count = 0; - seen_inference = FALSE; - while ((p = gettok(&q)) != NULL) { - // If not in POSIX mode expand wildcards in the name. - nfile = 1; - files = &p; - if (!posix && wildcard(p, &gd)) { - nfile = gd.gl_pathc; - files = gd.gl_pathv; - } - for (i = 0; i < nfile; ++i) { - int ttype = target_type(files[i]); - - np = newname(files[i]); - if (ttype != T_NORMAL) { - if (ttype == T_INFERENCE && posix) { - if (semicolon_cmd) - error_in_inference_rule("'; command'"); - seen_inference = TRUE; - } - np->n_flag |= N_SPECIAL; - } else if (!firstname) { - firstname = np; - } - addrule(np, dp, cp, dbl); - count++; - } - if (files != &p) - globfree(&gd); - } - if (seen_inference && count != 1) - error_in_inference_rule("multiple targets"); - - // Prerequisites and commands will be unused if there were - // no targets. Avoid leaking memory. - if (count == 0) { - freedeps(dp); - freecmds(cp); - } end_loop: free(str1); dispno = lineno; diff --git a/testsuite/make.tests b/testsuite/make.tests index 60bb78406..3c2aa5cf5 100755 --- a/testsuite/make.tests +++ b/testsuite/make.tests @@ -94,6 +94,14 @@ target: @exit 42 ' +# An equal sign in a command on a target rule was detected as a +# macro assignment. +testing "make equal sign in inline command" \ + "make -f -" "a = a\n" "" ' +a = a +target:;@echo a = $(a) +' + # A macro created using ::= remembers it's of type immediate-expansion. # Immediate expansion also occurs when += is used to append to such a macro. testing "make appending to immediate-expansion macro" \ -- cgit v1.2.3-55-g6feb