diff options
author | Ron Yorston <rmy@pobox.com> | 2024-05-31 10:27:18 +0100 |
---|---|---|
committer | Ron Yorston <rmy@pobox.com> | 2024-05-31 10:27:18 +0100 |
commit | f9d10b2b6314ea2a80515112498aaa919ad81c97 (patch) | |
tree | 3f199636f02b16f94f12cd9fe65b04fd087123f8 | |
parent | 2a0923c400fe5df140e1c5aad8dc59f4733e8598 (diff) | |
download | busybox-w32-f9d10b2b6314ea2a80515112498aaa919ad81c97.tar.gz busybox-w32-f9d10b2b6314ea2a80515112498aaa919ad81c97.tar.bz2 busybox-w32-f9d10b2b6314ea2a80515112498aaa919ad81c97.zip |
make: fix detection of target rules (take 2)
Commit d6b764116 (make: fix detection of target rules) checked
for target rules before macro assignments. This failed for some
Makefiles generated by autotools because partially defined macros
were expanded while testing for a target rule.
Revert to checking for macro assignments first, but try to detect
if the proposed left hand side of the assignment might form part
of a target rule with an inline command.
Also handle the case where the ';' separator of the inline command
has been obfuscated by putting it in a macro.
Saves 128-160 bytes.
(GitHub pdpmake issues 31, 44)
-rw-r--r-- | miscutils/make.c | 338 | ||||
-rwxr-xr-x | testsuite/make.tests | 23 |
2 files changed, 177 insertions, 184 deletions
diff --git a/miscutils/make.c b/miscutils/make.c index 8d5664500..02da3611c 100644 --- a/miscutils/make.c +++ b/miscutils/make.c | |||
@@ -1223,40 +1223,6 @@ modify_words(const char *val, int modifier, size_t lenf, size_t lenr, | |||
1223 | } | 1223 | } |
1224 | 1224 | ||
1225 | /* | 1225 | /* |
1226 | * Try to detect a target rule by searching for a colon that isn't part | ||
1227 | * of a macro assignment. Macros must have been expanded already. Return | ||
1228 | * a pointer to the colon or NULL. | ||
1229 | */ | ||
1230 | static char * | ||
1231 | find_colon(char *p) | ||
1232 | { | ||
1233 | char *q; | ||
1234 | |||
1235 | #if ENABLE_PLATFORM_MINGW32 | ||
1236 | for (q = p; (q = strchr(q, ':')); ++q) { | ||
1237 | if (posix && !(pragma & P_WINDOWS)) | ||
1238 | break; | ||
1239 | if (q == p || !isalpha(q[-1]) || q[1] != '/') | ||
1240 | break; | ||
1241 | } | ||
1242 | if (q != NULL) { | ||
1243 | #else | ||
1244 | if ((q = strchr(p, ':')) != NULL) { | ||
1245 | #endif | ||
1246 | // Skip ':=', '::=' and ':::=' macro assignments | ||
1247 | if ( | ||
1248 | // '::=' and ':::=' are from POSIX 202X. | ||
1249 | !(!POSIX_2017 && q[1] == ':' && q[2] == ':' && q[3] == '=') && | ||
1250 | !(!POSIX_2017 && q[1] == ':' && q[2] == '=') && | ||
1251 | // ':=' is a non-POSIX extension | ||
1252 | !(!posix && q[1] == '=') | ||
1253 | ) | ||
1254 | return q; | ||
1255 | } | ||
1256 | return NULL; | ||
1257 | } | ||
1258 | |||
1259 | /* | ||
1260 | * Return a pointer to the next instance of a given character. Macro | 1226 | * Return a pointer to the next instance of a given character. Macro |
1261 | * expansions are skipped so the ':' and '=' in $(VAR:.s1=.s2) aren't | 1227 | * expansions are skipped so the ':' and '=' in $(VAR:.s1=.s2) aren't |
1262 | * detected as separators for rules or macro definitions. | 1228 | * detected as separators for rules or macro definitions. |
@@ -2032,159 +1998,22 @@ input(FILE *fd, int ilevel) | |||
2032 | goto end_loop; | 1998 | goto end_loop; |
2033 | } | 1999 | } |
2034 | 2000 | ||
2035 | // Check for target rule | 2001 | // Check for a macro definition |
2036 | a = p = expanded = expand_macros(str, FALSE); | 2002 | if (find_char(str, '=') != NULL) { |
2037 | if ((q = find_colon(p)) != NULL) { | ||
2038 | // All tokens before ':' must be valid targets | ||
2039 | *q = '\0'; | ||
2040 | while ((a = gettok(&p)) != NULL && is_valid_target(a)) | ||
2041 | ; | ||
2042 | } | ||
2043 | free(expanded); | ||
2044 | |||
2045 | if (a == NULL) { | ||
2046 | // Looks like a target rule | ||
2047 | p = expanded = expand_macros(str, FALSE); | ||
2048 | |||
2049 | // Look for colon separator | ||
2050 | q = find_colon(p); | ||
2051 | if (q == NULL) | ||
2052 | error("expected separator"); | ||
2053 | |||
2054 | *q++ = '\0'; // Separate targets and prerequisites | ||
2055 | |||
2056 | // Double colon | ||
2057 | dbl = !posix && *q == ':'; | ||
2058 | if (dbl) | ||
2059 | q++; | ||
2060 | |||
2061 | // Look for semicolon separator | ||
2062 | cp = NULL; | ||
2063 | s = strchr(q, ';'); | ||
2064 | if (s) { | ||
2065 | *s = '\0'; | ||
2066 | // Retrieve command from copy of line | ||
2067 | if ((p = find_char(copy, ':')) && (p = strchr(p, ';'))) | ||
2068 | newcmd(&cp, process_command(p + 1)); | ||
2069 | } | ||
2070 | semicolon_cmd = cp != NULL; | ||
2071 | |||
2072 | // Create list of prerequisites | ||
2073 | dp = NULL; | ||
2074 | while (((p = gettok(&q)) != NULL)) { | ||
2075 | char *newp = NULL; | ||
2076 | |||
2077 | if (!posix) { | ||
2078 | // Allow prerequisites of form library(member1 member2). | ||
2079 | // Leading and trailing spaces in the brackets are skipped. | ||
2080 | if (!lib) { | ||
2081 | s = strchr(p, '('); | ||
2082 | if (s && !ends_with_bracket(s) && strchr(q, ')')) { | ||
2083 | // Looks like an unterminated archive member | ||
2084 | // with a terminator later on the line. | ||
2085 | lib = p; | ||
2086 | if (s[1] != '\0') { | ||
2087 | p = newp = auto_concat(lib, ")"); | ||
2088 | s[1] = '\0'; | ||
2089 | } else { | ||
2090 | continue; | ||
2091 | } | ||
2092 | } | ||
2093 | } else if (ends_with_bracket(p)) { | ||
2094 | if (*p != ')') | ||
2095 | p = newp = auto_concat(lib, p); | ||
2096 | lib = NULL; | ||
2097 | if (newp == NULL) | ||
2098 | continue; | ||
2099 | } else { | ||
2100 | p = newp = auto_string(xasprintf("%s%s)", lib, p)); | ||
2101 | } | ||
2102 | } | ||
2103 | |||
2104 | // If not in POSIX mode expand wildcards in the name. | ||
2105 | nfile = 1; | ||
2106 | files = &p; | ||
2107 | if (!posix && wildcard(p, &gd)) { | ||
2108 | nfile = gd.gl_pathc; | ||
2109 | files = gd.gl_pathv; | ||
2110 | } | ||
2111 | for (i = 0; i < nfile; ++i) { | ||
2112 | if (!POSIX_2017 && strcmp(files[i], ".WAIT") == 0) | ||
2113 | continue; | ||
2114 | np = newname(files[i]); | ||
2115 | newdep(&dp, np); | ||
2116 | } | ||
2117 | if (files != &p) | ||
2118 | globfree(&gd); | ||
2119 | free(newp); | ||
2120 | } | ||
2121 | lib = NULL; | ||
2122 | |||
2123 | // Create list of commands | ||
2124 | startno = dispno; | ||
2125 | while ((str2 = readline(fd)) && *str2 == '\t') { | ||
2126 | newcmd(&cp, process_command(str2)); | ||
2127 | free(str2); | ||
2128 | } | ||
2129 | dispno = startno; | ||
2130 | |||
2131 | // Create target names and attach rule to them | ||
2132 | q = expanded; | ||
2133 | count = 0; | ||
2134 | seen_inference = FALSE; | ||
2135 | while ((p = gettok(&q)) != NULL) { | ||
2136 | // If not in POSIX mode expand wildcards in the name. | ||
2137 | nfile = 1; | ||
2138 | files = &p; | ||
2139 | if (!posix && wildcard(p, &gd)) { | ||
2140 | nfile = gd.gl_pathc; | ||
2141 | files = gd.gl_pathv; | ||
2142 | } | ||
2143 | for (i = 0; i < nfile; ++i) { | ||
2144 | int ttype = target_type(files[i]); | ||
2145 | |||
2146 | np = newname(files[i]); | ||
2147 | if (ttype != T_NORMAL) { | ||
2148 | if (ttype == T_INFERENCE && posix) { | ||
2149 | if (semicolon_cmd) | ||
2150 | error_in_inference_rule("'; command'"); | ||
2151 | seen_inference = TRUE; | ||
2152 | } | ||
2153 | np->n_flag |= N_SPECIAL; | ||
2154 | } else if (!firstname) { | ||
2155 | firstname = np; | ||
2156 | } | ||
2157 | addrule(np, dp, cp, dbl); | ||
2158 | count++; | ||
2159 | } | ||
2160 | if (files != &p) | ||
2161 | globfree(&gd); | ||
2162 | } | ||
2163 | if (seen_inference && count != 1) | ||
2164 | error_in_inference_rule("multiple targets"); | ||
2165 | |||
2166 | // Prerequisites and commands will be unused if there were | ||
2167 | // no targets. Avoid leaking memory. | ||
2168 | if (count == 0) { | ||
2169 | freedeps(dp); | ||
2170 | freecmds(cp); | ||
2171 | } | ||
2172 | goto end_loop; | ||
2173 | } | ||
2174 | |||
2175 | // If we get here it must be a macro definition | ||
2176 | q = find_char(str, '='); | ||
2177 | if (q != NULL) { | ||
2178 | int level = (useenv || fd == NULL) ? 4 : 3; | 2003 | int level = (useenv || fd == NULL) ? 4 : 3; |
2004 | // Use a copy of the line: we might need the original | ||
2005 | // if this turns out to be a target rule. | ||
2006 | char *copy2 = xstrdup(str); | ||
2179 | char *newq = NULL; | 2007 | char *newq = NULL; |
2180 | char eq = '\0'; | 2008 | char eq = '\0'; |
2009 | q = find_char(copy2, '='); // q can't be NULL | ||
2181 | 2010 | ||
2182 | if (q - 1 > str) { | 2011 | if (q - 1 > copy2) { |
2183 | switch (q[-1]) { | 2012 | switch (q[-1]) { |
2184 | case ':': | 2013 | case ':': |
2185 | // '::=' and ':::=' are from POSIX 202X. | 2014 | // '::=' and ':::=' are from POSIX 202X. |
2186 | if (!POSIX_2017 && q - 2 > str && q[-2] == ':') { | 2015 | if (!POSIX_2017 && q - 2 > copy2 && q[-2] == ':') { |
2187 | if (q - 3 > str && q[-3] == ':') { | 2016 | if (q - 3 > copy2 && q[-3] == ':') { |
2188 | eq = 'B'; // BSD-style ':=' | 2017 | eq = 'B'; // BSD-style ':=' |
2189 | q[-3] = '\0'; | 2018 | q[-3] = '\0'; |
2190 | } else { | 2019 | } else { |
@@ -2216,8 +2045,19 @@ input(FILE *fd, int ilevel) | |||
2216 | *p = '\0'; | 2045 | *p = '\0'; |
2217 | 2046 | ||
2218 | // Expand left-hand side of assignment | 2047 | // Expand left-hand side of assignment |
2219 | p = expanded = expand_macros(str, FALSE); | 2048 | p = expanded = expand_macros(copy2, FALSE); |
2220 | if ((a = gettok(&p)) == NULL || gettok(&p)) | 2049 | if ((a = gettok(&p)) == NULL) |
2050 | error("invalid macro assignment"); | ||
2051 | |||
2052 | // If the expanded LHS contains ':' and ';' it can't be a | ||
2053 | // macro assignment but it might be a target rule. | ||
2054 | if ((s = strchr(a, ':')) != NULL && strchr(s, ';') != NULL) { | ||
2055 | free(expanded); | ||
2056 | free(copy2); | ||
2057 | goto try_target; | ||
2058 | } | ||
2059 | |||
2060 | if (gettok(&p)) | ||
2221 | error("invalid macro assignment"); | 2061 | error("invalid macro assignment"); |
2222 | 2062 | ||
2223 | if (eq == ':') { | 2063 | if (eq == ':') { |
@@ -2257,8 +2097,138 @@ input(FILE *fd, int ilevel) | |||
2257 | } | 2097 | } |
2258 | setmacro(a, q, level); | 2098 | setmacro(a, q, level); |
2259 | free(newq); | 2099 | free(newq); |
2260 | } else { | 2100 | free(copy2); |
2261 | error("missing separator"); | 2101 | goto end_loop; |
2102 | } | ||
2103 | |||
2104 | // If we get here it must be a target rule | ||
2105 | try_target: | ||
2106 | p = expanded = expand_macros(str, FALSE); | ||
2107 | |||
2108 | // Look for colon separator | ||
2109 | q = find_char(p, ':'); | ||
2110 | if (q == NULL) | ||
2111 | error("expected separator"); | ||
2112 | |||
2113 | *q++ = '\0'; // Separate targets and prerequisites | ||
2114 | |||
2115 | // Double colon | ||
2116 | dbl = !posix && *q == ':'; | ||
2117 | if (dbl) | ||
2118 | q++; | ||
2119 | |||
2120 | // Look for semicolon separator | ||
2121 | cp = NULL; | ||
2122 | s = strchr(q, ';'); | ||
2123 | if (s) { | ||
2124 | // Retrieve command from expanded copy of line | ||
2125 | char *copy3 = expand_macros(copy, FALSE); | ||
2126 | if ((p = find_char(copy3, ':')) && (p = strchr(p, ';'))) | ||
2127 | newcmd(&cp, process_command(p + 1)); | ||
2128 | free(copy3); | ||
2129 | *s = '\0'; | ||
2130 | } | ||
2131 | semicolon_cmd = cp != NULL; | ||
2132 | |||
2133 | // Create list of prerequisites | ||
2134 | dp = NULL; | ||
2135 | while (((p = gettok(&q)) != NULL)) { | ||
2136 | char *newp = NULL; | ||
2137 | |||
2138 | if (!posix) { | ||
2139 | // Allow prerequisites of form library(member1 member2). | ||
2140 | // Leading and trailing spaces in the brackets are skipped. | ||
2141 | if (!lib) { | ||
2142 | s = strchr(p, '('); | ||
2143 | if (s && !ends_with_bracket(s) && strchr(q, ')')) { | ||
2144 | // Looks like an unterminated archive member | ||
2145 | // with a terminator later on the line. | ||
2146 | lib = p; | ||
2147 | if (s[1] != '\0') { | ||
2148 | p = newp = auto_concat(lib, ")"); | ||
2149 | s[1] = '\0'; | ||
2150 | } else { | ||
2151 | continue; | ||
2152 | } | ||
2153 | } | ||
2154 | } else if (ends_with_bracket(p)) { | ||
2155 | if (*p != ')') | ||
2156 | p = newp = auto_concat(lib, p); | ||
2157 | lib = NULL; | ||
2158 | if (newp == NULL) | ||
2159 | continue; | ||
2160 | } else { | ||
2161 | p = newp = auto_string(xasprintf("%s%s)", lib, p)); | ||
2162 | } | ||
2163 | } | ||
2164 | |||
2165 | // If not in POSIX mode expand wildcards in the name. | ||
2166 | nfile = 1; | ||
2167 | files = &p; | ||
2168 | if (!posix && wildcard(p, &gd)) { | ||
2169 | nfile = gd.gl_pathc; | ||
2170 | files = gd.gl_pathv; | ||
2171 | } | ||
2172 | for (i = 0; i < nfile; ++i) { | ||
2173 | if (!POSIX_2017 && strcmp(files[i], ".WAIT") == 0) | ||
2174 | continue; | ||
2175 | np = newname(files[i]); | ||
2176 | newdep(&dp, np); | ||
2177 | } | ||
2178 | if (files != &p) | ||
2179 | globfree(&gd); | ||
2180 | free(newp); | ||
2181 | } | ||
2182 | lib = NULL; | ||
2183 | |||
2184 | // Create list of commands | ||
2185 | startno = dispno; | ||
2186 | while ((str2 = readline(fd)) && *str2 == '\t') { | ||
2187 | newcmd(&cp, process_command(str2)); | ||
2188 | free(str2); | ||
2189 | } | ||
2190 | dispno = startno; | ||
2191 | |||
2192 | // Create target names and attach rule to them | ||
2193 | q = expanded; | ||
2194 | count = 0; | ||
2195 | seen_inference = FALSE; | ||
2196 | while ((p = gettok(&q)) != NULL) { | ||
2197 | // If not in POSIX mode expand wildcards in the name. | ||
2198 | nfile = 1; | ||
2199 | files = &p; | ||
2200 | if (!posix && wildcard(p, &gd)) { | ||
2201 | nfile = gd.gl_pathc; | ||
2202 | files = gd.gl_pathv; | ||
2203 | } | ||
2204 | for (i = 0; i < nfile; ++i) { | ||
2205 | int ttype = target_type(files[i]); | ||
2206 | |||
2207 | np = newname(files[i]); | ||
2208 | if (ttype != T_NORMAL) { | ||
2209 | if (ttype == T_INFERENCE && posix) { | ||
2210 | if (semicolon_cmd) | ||
2211 | error_in_inference_rule("'; command'"); | ||
2212 | seen_inference = TRUE; | ||
2213 | } | ||
2214 | np->n_flag |= N_SPECIAL; | ||
2215 | } else if (!firstname) { | ||
2216 | firstname = np; | ||
2217 | } | ||
2218 | addrule(np, dp, cp, dbl); | ||
2219 | count++; | ||
2220 | } | ||
2221 | if (files != &p) | ||
2222 | globfree(&gd); | ||
2223 | } | ||
2224 | if (seen_inference && count != 1) | ||
2225 | error_in_inference_rule("multiple targets"); | ||
2226 | |||
2227 | // Prerequisites and commands will be unused if there were | ||
2228 | // no targets. Avoid leaking memory. | ||
2229 | if (count == 0) { | ||
2230 | freedeps(dp); | ||
2231 | freecmds(cp); | ||
2262 | } | 2232 | } |
2263 | 2233 | ||
2264 | end_loop: | 2234 | end_loop: |
diff --git a/testsuite/make.tests b/testsuite/make.tests index 0397ab4de..6438c90c9 100755 --- a/testsuite/make.tests +++ b/testsuite/make.tests | |||
@@ -103,6 +103,29 @@ a = a | |||
103 | target:;@echo a = $(a) | 103 | target:;@echo a = $(a) |
104 | ' | 104 | ' |
105 | 105 | ||
106 | # Ensure an inline command on a target rule can be detected even if | ||
107 | # the semicolon is obfuscated. | ||
108 | testing "make equal sign in obfuscated inline command" \ | ||
109 | "make -f -" "a = a\n" "" ' | ||
110 | a = a | ||
111 | semi = ; | ||
112 | target:$(semi)@echo a = $(a) | ||
113 | ' | ||
114 | |||
115 | # The fix for the above test broke a complex chain of macro assignments | ||
116 | # generated by autotools. | ||
117 | testing "make complex chain of macro assignments" \ | ||
118 | "make -f -" "flag 1\n" "" ' | ||
119 | FLAG_ = $(FLAG_$(VALUE)) | ||
120 | FLAG_0 = flag 0 | ||
121 | FLAG_1 = flag 1 | ||
122 | MYFLAG = $(FLAG_$(VALUE)) | ||
123 | VALUE = 1 | ||
124 | |||
125 | target: | ||
126 | @echo $(MYFLAG) | ||
127 | ' | ||
128 | |||
106 | # When a build command fails and the '-k' option has been provided | 129 | # When a build command fails and the '-k' option has been provided |
107 | # (continue execution on error) no further commands should be executed | 130 | # (continue execution on error) no further commands should be executed |
108 | # for the current target. | 131 | # for the current target. |