diff options
author | Ron Yorston <rmy@pobox.com> | 2023-10-23 16:17:12 +0100 |
---|---|---|
committer | Ron Yorston <rmy@pobox.com> | 2023-10-23 16:17:12 +0100 |
commit | d6b76411684d9cc7a676e98bd9f39daecfe8af36 (patch) | |
tree | b3a916bdbad2b229b8714f79274d54c75dd8c929 | |
parent | 87a3ddc0620fb3ff022663bf2241b92b483e3cf0 (diff) | |
download | busybox-w32-d6b76411684d9cc7a676e98bd9f39daecfe8af36.tar.gz busybox-w32-d6b76411684d9cc7a676e98bd9f39daecfe8af36.tar.bz2 busybox-w32-d6b76411684d9cc7a676e98bd9f39daecfe8af36.zip |
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.
-rw-r--r-- | miscutils/make.c | 335 | ||||
-rwxr-xr-x | 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) | |||
438 | { | 438 | { |
439 | const char *s; | 439 | const char *s; |
440 | 440 | ||
441 | if (!posix) | 441 | if (!posix) { |
442 | for (s = name; *s; ++s) { | ||
443 | if (*s == '=') | ||
444 | return FALSE; | ||
445 | } | ||
442 | return TRUE; | 446 | return TRUE; |
447 | } | ||
443 | 448 | ||
444 | for (s = name; *s; ++s) { | 449 | for (s = name; *s; ++s) { |
445 | #if ENABLE_PLATFORM_MINGW32 | 450 | #if ENABLE_PLATFORM_MINGW32 |
@@ -1157,44 +1162,55 @@ modify_words(const char *val, int modifier, size_t lenf, size_t lenr, | |||
1157 | } | 1162 | } |
1158 | 1163 | ||
1159 | /* | 1164 | /* |
1160 | * Return a pointer to the next instance of a given character. Macro | 1165 | * Try to detect a target rule by searching for a colon that isn't part |
1161 | * expansions are skipped so the ':' and '=' in $(VAR:.s1=.s2) aren't | 1166 | * of a macro assignment. Macros must have been expanded already. Return |
1162 | * detected as separators for rules or macro definitions. | 1167 | * a pointer to the colon or NULL. |
1163 | */ | 1168 | */ |
1164 | static char * | 1169 | static char * |
1165 | find_char(const char *str, int c) | 1170 | find_colon(char *p) |
1166 | { | 1171 | { |
1167 | const char *s; | 1172 | char *q; |
1168 | 1173 | ||
1169 | for (s = skip_macro(str); *s; s = skip_macro(s + 1)) { | 1174 | #if ENABLE_PLATFORM_MINGW32 |
1170 | if (*s == c) | 1175 | for (q = p; (q = strchr(q, ':')); ++q) { |
1171 | return (char *)s; | 1176 | if (posix && !(pragma & P_WINDOWS)) |
1177 | break; | ||
1178 | if (q[1] != '/') | ||
1179 | break; | ||
1180 | } | ||
1181 | if (q != NULL) { | ||
1182 | #else | ||
1183 | if ((q = strchr(p, ':')) != NULL) { | ||
1184 | #endif | ||
1185 | // Skip ':=', '::=' and ':::=' macro assignments | ||
1186 | if ( | ||
1187 | // '::=' and ':::=' are from POSIX 202X. | ||
1188 | !(!POSIX_2017 && q[1] == ':' && q[2] == ':' && q[3] == '=') && | ||
1189 | !(!POSIX_2017 && q[1] == ':' && q[2] == '=') && | ||
1190 | // ':=' is a non-POSIX extension | ||
1191 | !(!posix && q[1] == '=') | ||
1192 | ) | ||
1193 | return q; | ||
1172 | } | 1194 | } |
1173 | return NULL; | 1195 | return NULL; |
1174 | } | 1196 | } |
1175 | 1197 | ||
1176 | #if ENABLE_PLATFORM_MINGW32 | ||
1177 | /* | 1198 | /* |
1178 | * Ignore colons in targets of the form c:/path when looking for a | 1199 | * Return a pointer to the next instance of a given character. Macro |
1179 | * target rule. | 1200 | * expansions are skipped so the ':' and '=' in $(VAR:.s1=.s2) aren't |
1201 | * detected as separators for rules or macro definitions. | ||
1180 | */ | 1202 | */ |
1181 | static char * | 1203 | static char * |
1182 | find_colon(const char *str) | 1204 | find_char(const char *str, int c) |
1183 | { | 1205 | { |
1184 | const char *s = str; | 1206 | const char *s; |
1185 | 1207 | ||
1186 | while ((s = find_char(s, ':'))) { | 1208 | for (s = skip_macro(str); *s; s = skip_macro(s + 1)) { |
1187 | if (posix && !(pragma & P_WINDOWS)) | 1209 | if (*s == c) |
1188 | break; | 1210 | return (char *)s; |
1189 | if (s[1] != '/') | ||
1190 | break; | ||
1191 | ++s; | ||
1192 | } | 1211 | } |
1193 | return (char *)s; | 1212 | return NULL; |
1194 | } | 1213 | } |
1195 | #else | ||
1196 | # define find_colon(s) find_char(s, ':') | ||
1197 | #endif | ||
1198 | 1214 | ||
1199 | /* | 1215 | /* |
1200 | * Recursively expand any macros in str to an allocated string. | 1216 | * Recursively expand any macros in str to an allocated string. |
@@ -1881,7 +1897,147 @@ input(FILE *fd, int ilevel) | |||
1881 | goto end_loop; | 1897 | goto end_loop; |
1882 | } | 1898 | } |
1883 | 1899 | ||
1884 | // Check for a macro definition | 1900 | // Check for target rule |
1901 | a = p = expanded = expand_macros(str, FALSE); | ||
1902 | if ((q = find_colon(p)) != NULL) { | ||
1903 | // All tokens before ':' must be valid targets | ||
1904 | *q = '\0'; | ||
1905 | while ((a = gettok(&p)) != NULL && is_valid_target(a)) | ||
1906 | ; | ||
1907 | } | ||
1908 | free(expanded); | ||
1909 | |||
1910 | if (a == NULL) { | ||
1911 | // Looks like a target rule | ||
1912 | p = expanded = expand_macros(str, FALSE); | ||
1913 | |||
1914 | // Look for colon separator | ||
1915 | q = find_colon(p); | ||
1916 | if (q == NULL) | ||
1917 | error("expected separator"); | ||
1918 | |||
1919 | *q++ = '\0'; // Separate targets and prerequisites | ||
1920 | |||
1921 | // Double colon | ||
1922 | dbl = !posix && *q == ':'; | ||
1923 | if (dbl) | ||
1924 | q++; | ||
1925 | |||
1926 | // Look for semicolon separator | ||
1927 | cp = NULL; | ||
1928 | s = strchr(q, ';'); | ||
1929 | if (s) { | ||
1930 | *s = '\0'; | ||
1931 | // Retrieve command from copy of line | ||
1932 | if ((p = find_char(copy, ':')) && (p = strchr(p, ';'))) | ||
1933 | newcmd(&cp, process_command(p + 1)); | ||
1934 | } | ||
1935 | semicolon_cmd = cp != NULL; | ||
1936 | |||
1937 | // Create list of prerequisites | ||
1938 | dp = NULL; | ||
1939 | while (((p = gettok(&q)) != NULL)) { | ||
1940 | char *newp = NULL; | ||
1941 | |||
1942 | if (!posix) { | ||
1943 | // Allow prerequisites of form library(member1 member2). | ||
1944 | // Leading and trailing spaces in the brackets are skipped. | ||
1945 | if (!lib) { | ||
1946 | s = strchr(p, '('); | ||
1947 | if (s && !ends_with_bracket(s) && strchr(q, ')')) { | ||
1948 | // Looks like an unterminated archive member | ||
1949 | // with a terminator later on the line. | ||
1950 | lib = p; | ||
1951 | if (s[1] != '\0') { | ||
1952 | p = newp = auto_concat(lib, ")"); | ||
1953 | s[1] = '\0'; | ||
1954 | } else { | ||
1955 | continue; | ||
1956 | } | ||
1957 | } | ||
1958 | } else if (ends_with_bracket(p)) { | ||
1959 | if (*p != ')') | ||
1960 | p = newp = auto_concat(lib, p); | ||
1961 | lib = NULL; | ||
1962 | if (newp == NULL) | ||
1963 | continue; | ||
1964 | } else { | ||
1965 | p = newp = auto_string(xasprintf("%s%s)", lib, p)); | ||
1966 | } | ||
1967 | } | ||
1968 | |||
1969 | // If not in POSIX mode expand wildcards in the name. | ||
1970 | nfile = 1; | ||
1971 | files = &p; | ||
1972 | if (!posix && wildcard(p, &gd)) { | ||
1973 | nfile = gd.gl_pathc; | ||
1974 | files = gd.gl_pathv; | ||
1975 | } | ||
1976 | for (i = 0; i < nfile; ++i) { | ||
1977 | if (!POSIX_2017 && strcmp(files[i], ".WAIT") == 0) | ||
1978 | continue; | ||
1979 | np = newname(files[i]); | ||
1980 | newdep(&dp, np); | ||
1981 | } | ||
1982 | if (files != &p) | ||
1983 | globfree(&gd); | ||
1984 | free(newp); | ||
1985 | } | ||
1986 | lib = NULL; | ||
1987 | |||
1988 | // Create list of commands | ||
1989 | startno = dispno; | ||
1990 | while ((str2 = readline(fd)) && *str2 == '\t') { | ||
1991 | newcmd(&cp, process_command(str2)); | ||
1992 | free(str2); | ||
1993 | } | ||
1994 | dispno = startno; | ||
1995 | |||
1996 | // Create target names and attach rule to them | ||
1997 | q = expanded; | ||
1998 | count = 0; | ||
1999 | seen_inference = FALSE; | ||
2000 | while ((p = gettok(&q)) != NULL) { | ||
2001 | // If not in POSIX mode expand wildcards in the name. | ||
2002 | nfile = 1; | ||
2003 | files = &p; | ||
2004 | if (!posix && wildcard(p, &gd)) { | ||
2005 | nfile = gd.gl_pathc; | ||
2006 | files = gd.gl_pathv; | ||
2007 | } | ||
2008 | for (i = 0; i < nfile; ++i) { | ||
2009 | int ttype = target_type(files[i]); | ||
2010 | |||
2011 | np = newname(files[i]); | ||
2012 | if (ttype != T_NORMAL) { | ||
2013 | if (ttype == T_INFERENCE && posix) { | ||
2014 | if (semicolon_cmd) | ||
2015 | error_in_inference_rule("'; command'"); | ||
2016 | seen_inference = TRUE; | ||
2017 | } | ||
2018 | np->n_flag |= N_SPECIAL; | ||
2019 | } else if (!firstname) { | ||
2020 | firstname = np; | ||
2021 | } | ||
2022 | addrule(np, dp, cp, dbl); | ||
2023 | count++; | ||
2024 | } | ||
2025 | if (files != &p) | ||
2026 | globfree(&gd); | ||
2027 | } | ||
2028 | if (seen_inference && count != 1) | ||
2029 | error_in_inference_rule("multiple targets"); | ||
2030 | |||
2031 | // Prerequisites and commands will be unused if there were | ||
2032 | // no targets. Avoid leaking memory. | ||
2033 | if (count == 0) { | ||
2034 | freedeps(dp); | ||
2035 | freecmds(cp); | ||
2036 | } | ||
2037 | goto end_loop; | ||
2038 | } | ||
2039 | |||
2040 | // If we get here it must be a macro definition | ||
1885 | q = find_char(str, '='); | 2041 | q = find_char(str, '='); |
1886 | if (q != NULL) { | 2042 | if (q != NULL) { |
1887 | int level = (useenv || fd == NULL) ? 4 : 3; | 2043 | int level = (useenv || fd == NULL) ? 4 : 3; |
@@ -1966,135 +2122,10 @@ input(FILE *fd, int ilevel) | |||
1966 | } | 2122 | } |
1967 | setmacro(a, q, level); | 2123 | setmacro(a, q, level); |
1968 | free(newq); | 2124 | free(newq); |
1969 | goto end_loop; | 2125 | } else { |
1970 | } | 2126 | error("expected macro definition"); |
1971 | |||
1972 | // If we get here it must be a target rule | ||
1973 | p = expanded = expand_macros(str, FALSE); | ||
1974 | |||
1975 | // Look for colon separator | ||
1976 | q = find_colon(p); | ||
1977 | if (q == NULL) | ||
1978 | error("expected separator"); | ||
1979 | |||
1980 | *q++ = '\0'; // Separate targets and prerequisites | ||
1981 | |||
1982 | // Double colon | ||
1983 | dbl = !posix && *q == ':'; | ||
1984 | if (dbl) | ||
1985 | q++; | ||
1986 | |||
1987 | // Look for semicolon separator | ||
1988 | cp = NULL; | ||
1989 | s = strchr(q, ';'); | ||
1990 | if (s) { | ||
1991 | *s = '\0'; | ||
1992 | // Retrieve command from copy of line | ||
1993 | if ((p = find_colon(copy)) && (p = strchr(p, ';'))) | ||
1994 | newcmd(&cp, process_command(p + 1)); | ||
1995 | } | ||
1996 | semicolon_cmd = cp != NULL; | ||
1997 | |||
1998 | // Create list of prerequisites | ||
1999 | dp = NULL; | ||
2000 | while (((p = gettok(&q)) != NULL)) { | ||
2001 | char *newp = NULL; | ||
2002 | |||
2003 | if (!posix) { | ||
2004 | // Allow prerequisites of form library(member1 member2). | ||
2005 | // Leading and trailing spaces in the brackets are skipped. | ||
2006 | if (!lib) { | ||
2007 | s = strchr(p, '('); | ||
2008 | if (s && !ends_with_bracket(s) && strchr(q, ')')) { | ||
2009 | // Looks like an unterminated archive member | ||
2010 | // with a terminator later on the line. | ||
2011 | lib = p; | ||
2012 | if (s[1] != '\0') { | ||
2013 | p = newp = auto_concat(lib, ")"); | ||
2014 | s[1] = '\0'; | ||
2015 | } else { | ||
2016 | continue; | ||
2017 | } | ||
2018 | } | ||
2019 | } else if (ends_with_bracket(p)) { | ||
2020 | if (*p != ')') | ||
2021 | p = newp = auto_concat(lib, p); | ||
2022 | lib = NULL; | ||
2023 | if (newp == NULL) | ||
2024 | continue; | ||
2025 | } else { | ||
2026 | p = newp = auto_string(xasprintf("%s%s)", lib, p)); | ||
2027 | } | ||
2028 | } | ||
2029 | |||
2030 | // If not in POSIX mode expand wildcards in the name. | ||
2031 | nfile = 1; | ||
2032 | files = &p; | ||
2033 | if (!posix && wildcard(p, &gd)) { | ||
2034 | nfile = gd.gl_pathc; | ||
2035 | files = gd.gl_pathv; | ||
2036 | } | ||
2037 | for (i = 0; i < nfile; ++i) { | ||
2038 | if (!POSIX_2017 && strcmp(files[i], ".WAIT") == 0) | ||
2039 | continue; | ||
2040 | np = newname(files[i]); | ||
2041 | newdep(&dp, np); | ||
2042 | } | ||
2043 | if (files != &p) | ||
2044 | globfree(&gd); | ||
2045 | free(newp); | ||
2046 | } | 2127 | } |
2047 | lib = NULL; | ||
2048 | 2128 | ||
2049 | // Create list of commands | ||
2050 | startno = dispno; | ||
2051 | while ((str2 = readline(fd)) && *str2 == '\t') { | ||
2052 | newcmd(&cp, process_command(str2)); | ||
2053 | free(str2); | ||
2054 | } | ||
2055 | dispno = startno; | ||
2056 | |||
2057 | // Create target names and attach rule to them | ||
2058 | q = expanded; | ||
2059 | count = 0; | ||
2060 | seen_inference = FALSE; | ||
2061 | while ((p = gettok(&q)) != NULL) { | ||
2062 | // If not in POSIX mode expand wildcards in the name. | ||
2063 | nfile = 1; | ||
2064 | files = &p; | ||
2065 | if (!posix && wildcard(p, &gd)) { | ||
2066 | nfile = gd.gl_pathc; | ||
2067 | files = gd.gl_pathv; | ||
2068 | } | ||
2069 | for (i = 0; i < nfile; ++i) { | ||
2070 | int ttype = target_type(files[i]); | ||
2071 | |||
2072 | np = newname(files[i]); | ||
2073 | if (ttype != T_NORMAL) { | ||
2074 | if (ttype == T_INFERENCE && posix) { | ||
2075 | if (semicolon_cmd) | ||
2076 | error_in_inference_rule("'; command'"); | ||
2077 | seen_inference = TRUE; | ||
2078 | } | ||
2079 | np->n_flag |= N_SPECIAL; | ||
2080 | } else if (!firstname) { | ||
2081 | firstname = np; | ||
2082 | } | ||
2083 | addrule(np, dp, cp, dbl); | ||
2084 | count++; | ||
2085 | } | ||
2086 | if (files != &p) | ||
2087 | globfree(&gd); | ||
2088 | } | ||
2089 | if (seen_inference && count != 1) | ||
2090 | error_in_inference_rule("multiple targets"); | ||
2091 | |||
2092 | // Prerequisites and commands will be unused if there were | ||
2093 | // no targets. Avoid leaking memory. | ||
2094 | if (count == 0) { | ||
2095 | freedeps(dp); | ||
2096 | freecmds(cp); | ||
2097 | } | ||
2098 | end_loop: | 2129 | end_loop: |
2099 | free(str1); | 2130 | free(str1); |
2100 | dispno = lineno; | 2131 | 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: | |||
94 | @exit 42 | 94 | @exit 42 |
95 | ' | 95 | ' |
96 | 96 | ||
97 | # An equal sign in a command on a target rule was detected as a | ||
98 | # macro assignment. | ||
99 | testing "make equal sign in inline command" \ | ||
100 | "make -f -" "a = a\n" "" ' | ||
101 | a = a | ||
102 | target:;@echo a = $(a) | ||
103 | ' | ||
104 | |||
97 | # A macro created using ::= remembers it's of type immediate-expansion. | 105 | # A macro created using ::= remembers it's of type immediate-expansion. |
98 | # Immediate expansion also occurs when += is used to append to such a macro. | 106 | # Immediate expansion also occurs when += is used to append to such a macro. |
99 | testing "make appending to immediate-expansion macro" \ | 107 | testing "make appending to immediate-expansion macro" \ |