From: Zack Weinberg Date: Wed, 26 Jun 2024 18:42:44 +0000 (-0400) Subject: WIP: replace AS_ESCAPE and _AS_QUOTE with AS_QUOTE_D. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ec192f7fc9b5cab9aa34c8135e60aba3670edf53;p=thirdparty%2Fautoconf.git WIP: replace AS_ESCAPE and _AS_QUOTE with AS_QUOTE_D. AS_ESCAPE is difficult or even impossible to use correctly, depending on what you’re trying to do with it: + AS_ESCAPE([text], [`\"]) is intended to permit variable interpolation but prevent command substitution. This does not work anymore, because we have $(...) command substitution nowadays, Solaris 10 notwithstanding. + It is incorrect to leave \ or " out of the [chars] argument, but people do this all the dang time both in our own code and in third-party code. + It is incorrect to put anything in [chars] besides $ ` \ ", but people also do this. In particular, 'AS_ESCAPE([text], [\'])' *does not* produce a correct single-quoted string, but at least one third-party m4 macro does this anyway. + In most cases you need to write AS_ESCAPE(m4_dquote(m4_expand([text]))) or else M4 macros inside [text] will be expanded *after* the quotation process is complete, and the text they expand to won’t get escaped. *Our* code using AS_ESCAPE was diligent about this, but almost no no third-party uses bothered. + Almost all uses of AS_ESCAPE are constructing double-quoted strings, so it would be more ergonomic if it added the outermost quotes for you. (All assertions about third-party code courtesy of .) The internal _AS_QUOTE macro is also almost impossible to use correctly, for the above reasons plus the fact that it includes some 20-year-old *internal* backward compatibility logic that doesn’t distinguish ${variable} from $(command) either. Replace with a safer API: + New macro AS_QUOTE_S([text]) turns TEXT into a single-quoted string, *correctly* escaping embedded single quotes, and supplying an outer pair of quotes. Macros in TEXT are expanded once before quotation. + New macro AS_QUOTE_D([text], [interpolation]) turns TEXT into a double-quoted string, supplying an outer pair of quotes. Macros in TEXT are expanded once before quotation. The INTERPOLATION argument is a comma-separated list of keywords specifying what types of interpolation will be permitted: - ‘allow-vars’: Allow variable and arithmetic interpolation. - ‘allow-commands’: Allow command substitution. If the argument is empty or omitted, no interpolation is allowed. This is intentionally verbose and there is no ‘allow-all’, because I want people to think hard about if it’s really a good idea before enabling command substitution, and I want it to be easy to find all the place’s it’s being used. (Almost all uses of AS_ESCAPE, both internally and externally, want either no interpolation or only variable interpolation.) Both `...` and $(...) are handled correctly in all modes. + AS_ESCAPE is changed to map each of the valid possibilities for CHARS to the AS_QUOTE_D interpolation mode that was probably intended, papering over the bugs in third party macros. (For example, both [`\"] and [`] by itself will be mapped to allow-vars mode, because we presume that someone who wrote [‘] didn’t think it through and there’s a latent bug.) Invalid CHARS arguments now raise an error instead of silently doing something nonsensical. All uses trigger a -Wobsolete warning. AS_ESCAPE cannot be autoupdated to AS_QUOTE_D (because it’s an m4sh macro, and because autoupdate can’t edit out the surrounding quote marks) so instead I wrote a whole bunch of advice for manual conversion in the manual. WIP: buggy (partially but not entirely because of the bugs in m4_join_uniq); one internal use of AS_ESCAPE remains; need to write tests and NEWS; unresolved design decisions including + should ‘allow-arithmetic’ be a third interpolation keyword, separate from ‘allow-vars’? + do we want a public API for escaping *without* adding surrounding quotes? Autotest was doing this but it didn’t particularly need to. + should the result of AS_QUOTE_[DS] be M4-quoted? Currently it isn’t. + is expand-once-then-quote [i.e. shellquote(m4_dquote(m4_expand([text])))] the most ergonomic behavior? --- diff --git a/doc/autoconf.texi b/doc/autoconf.texi index a500de35..c8062866 100644 --- a/doc/autoconf.texi +++ b/doc/autoconf.texi @@ -13959,6 +13959,7 @@ namespaces. @menu * Common Shell Constructs:: Portability layer for common shell constructs +* Shell Quotation:: Turning text into quoted strings * Polymorphic Variables:: Support for indirect variable names * Initialization Macros:: Macros to establish a sane shell environment * File Descriptor Macros:: File descriptor macros for input and output @@ -14041,55 +14042,6 @@ for portability, should not include more than one newline. The bytes of Redirections can be placed outside the macro invocation. @end defmac -@c We cannot use @dvar because the macro expansion mistreats backslashes. -@defmac AS_ESCAPE (@var{string}, @r{[}@var{chars} = @samp{`\"$}@r{]}) -@asindex{ESCAPE} -Expands to @var{string}, with any characters in @var{chars} escaped with -a backslash (@samp{\}). @var{chars} should be at most four bytes long, -and only contain characters from the set @samp{`\"$}; however, -characters may be safely listed more than once in @var{chars} for the -sake of syntax highlighting editors. The current implementation expands -@var{string} after adding escapes; if @var{string} contains macro calls -that in turn expand to text needing shell quoting, you can use -@code{AS_ESCAPE(m4_dquote(m4_expand([string])))}. - -The default for @var{chars} (@samp{\"$`}) is the set of characters -needing escapes when @var{string} will be used literally within double -quotes. One common variant is the set of characters to protect when -@var{string} will be used literally within back-ticks or an unquoted -here-document (@samp{\$`}). Another common variant is @samp{""}, which can -be used to form a double-quoted string containing the same expansions -that would have occurred if @var{string} were expanded in an unquoted -here-document; however, when using this variant, care must be taken that -@var{string} does not use double quotes within complex variable -expansions (such as @samp{$@{foo-`echo "hi"`@}}) that would be broken -with improper escapes. - -This macro is often used with @code{AS_ECHO}. For an example, observe -the output generated by the shell code generated from this snippet: - -@example -foo=bar -AS_ECHO(["AS_ESCAPE(["$foo" = ])AS_ESCAPE(["$foo"], [""])"]) -@result{}"$foo" = "bar" -m4_define([macro], [a, [\b]]) -AS_ECHO(["AS_ESCAPE([[macro]])"]) -@result{}macro -AS_ECHO(["AS_ESCAPE([macro])"]) -@result{}a, b -AS_ECHO(["AS_ESCAPE(m4_dquote(m4_expand([macro])))"]) -@result{}a, \b -@end example - -@comment Should we add AS_ESCAPE_SINGLE? If we do, we can optimize in -@comment the case of @var{string} that does not contain '. -To escape a string that will be placed within single quotes, use: - -@example -m4_bpatsubst([[@var{string}]], ['], ['\\'']) -@end example -@end defmac - @defmac AS_EXECUTABLE_P (@var{file}) @asindex{EXECUTABLE_P} Emit code to probe whether @var{file} is a regular file with executable @@ -14202,6 +14154,79 @@ glibc (@pxref{String/Array Comparison, , String/Array Comparison, libc, The GNU C Library}). @end defmac +@node Shell Quotation +@section Shell Quotation + +M4sh provides two macros for fabricating shell quoted strings from text. + +@defmac AS_QUOTE_S (@var{string}) +@asindex{QUOTE_S} +Convert @var{string} into a single-quoted shell string. +@samp{'} within @var{string} will be mapped to @samp{'\''}. + +@var{string} is taken literally---it cannot be the contents of a shell +variable. M4 macros within @var{string} will be expanded, once, before +quotation. +@end defmac + + +@defmac AS_QUOTE_D (@var{string}, @ovar{interpolation}) +@asindex{QUOTE_D} + +Convert @var{string} into a double-quoted shell string, escaping +special characters with @samp{\} as necessary. @var{string} is taken +literally---it cannot be the contents of a shell variable. M4 macros +within @var{string} will be expanded, once, before quotation. + +The @var{interpolation} argument controls whether anything can be +interpolated into the string when the shell script runs. By default, or +when this argument is empty, nothing will be interpolated. The possible +non-empty values for @var{interpolation} are: + +@table @code +@item allow-vars +Allow variables and arithmetic expressions to be interpolated. +For example: +@smallexample +@group +AWK=gawk +echo AS_QUOTE_D([checking whether $AWK supports 'asorti'], + [allow-vars]) + @expansion{} AWK=gawk + @expansion{} echo "checking whether $AWK supports 'asorti'" + @result{} checking whether gawk supports 'asorti' +@end group +@end smallexample +@noindent +Without @samp{allow-vars}, the output would instead be @samp{checking +whether $AWK supports 'asorti'}. + +@strong{Compatibility note:} Arithmetic expressions are part of the +POSIX shell standard, but are not universally implemented; see +@ref{shell-arithmetic,, the discussion of @samp{$((@var{expression}))}} +in @ref{Shell Substitutions}. In Autoconf scripts it is better to use +@code{AS_VAR_ARITH} and then interpolate the variable holding the +result. + +@item allow-commands +Allow execution of embedded commands, written either with +@samp{`@var{command}`} or with @samp{$(@var{command})}. (Even today, +not all shells implement @samp{$(@var{command})}, so truly portable +scripts still need to avoid it; but @emph{defensive} quotation requires +us to assume that it @emph{does} work.) + +@strong{Caution:} Embedded commands that contain nested double-quoted +strings will be mangled (handling them correctly is beyond M4's parsing +capabilities). Instead, assign the result of the command to a shell +variable and use variable interpolation. + +@item allow-vars,allow-commands +@itemx allow-commands,allow-vars +Allow both of the above kinds of interpolation. +@end table +@end defmac + + @node Polymorphic Variables @section Support for indirect variable names @cindex variable name indirection @@ -16961,6 +16986,7 @@ echo $(case x in x) echo hello;; esac) @end example +@anchor{shell-arithmetic} @item $((@var{expression})) @cindex @code{$((@var{expression}))} Arithmetic expansion is not portable as some shells (most @@ -24817,6 +24843,90 @@ This macro was renamed @code{AC_DECL_YYTEXT}, which in turn was integrated into @code{AC_PROG_LEX} (@pxref{AC_PROG_LEX}). @end defmac + +@defmac AS_ESCAPE (@var{string}, @ovar{chars}) +@asindex{ESCAPE} +This macro has been superseded by @code{AS_QUOTE_D} and @code{AS_QUOTE_S} +(@pxref{Shell Quotation}). @command{autoupdate} does not attempt to +update uses of @code{AS_ESCAPE}, because the update will usually require +modifying surrounding code, which @command{autoupdate} cannot do. + +Like @code{AS_QUOTE_D}, @code{AS_ESCAPE} escapes special characters for +use inside shell double-quoted strings. However, it is harder to use +correctly: it does not put quotation marks around @var{string}, it does +not expand M4 macros in @var{string} before escaping special characters, +and many of the values of the @var{chars} argument will produce +improperly quoted results for some strings. + +@samp{"AS_ESCAPE(m4_dquote(m4_expand([string])))"} is completely +equivalent to @samp{AS_QUOTE_D([string])}. Watch out for the +surrounding quotation marks! They were necessary with @code{AS_ESCAPE} +but become incorrect with @code{AS_QUOTE_D}. + +In most cases, it is also correct to replace +@samp{"AS_ESCAPE([string])"}, without the pre-expansion, with +@samp{AS_QUOTE_D([string])}. If @var{string} should not be expanded +at all, use either @samp{AS_QUOTE_D([[string]])} or +@samp{AS_QUOTE_D(m4_dquote([string]))} +as appropriate. + +If you actually need M4 macros within @var{string} to be expanded +and any special characters from the expansion @emph{not} to be escaped, +split up the string like this: +@samp{AS_QUOTE_D([prefix])"macro"AS_QUOTE_D([suffix])}. This will +produce @samp{"prefix""expanded text""suffix"} in the output; the +doubled quote marks will be removed by the shell. + +Uses of @code{AS_ESCAPE} with the @var{chars} argument fall into +four classes, as shown in the table below: + +@c Without the trailing hard spaces on the first line, +@c the HTML render has no whitespace between the columns! +@multitable {@code{`$"\@ @ `$"@ @ `$\@ @ `$@ @ @ }} {@samp{AS_QUOTE_D([...], [allow-commands,allow-vars])}} +@headitem @var{chars} arg @tab Replace with@dots{} +@item +@t{`$"\@ @ `$"@ @ `$\@ @ `$@ @ @ } +@tab +@samp{AS_QUOTE_D([...])} +@item +@t{@ `"\@ @ @ `"@ @ @ `\@ @ @ `} +@tab +@samp{AS_QUOTE_D([...], [allow-vars])} +@item +@t{@ $"\@ @ @ $"@ @ @ $\@ @ @ $} +@tab +@samp{AS_QUOTE_D([...], [allow-commands])} +@item +@t{@ @ "\@ @ @ @ "@ @ @ @ \} +@tab +@samp{AS_QUOTE_D([...], [allow-commands,allow-vars])} +@end multitable + +The order of characters within @var{chars} is not significant, and +duplicate characters are ignored: @samp{"`} and @samp{`""} are both the +same as @samp{`"} and should be mapped to @code{allow-vars}. + +Uses of @code{AS_ESCAPE} with any characters in @var{chars} besides +@samp{"}, @samp{`}, @samp{$}, and/or @samp{\} are incorrect, and have no +@code{AS_QUOTE_D} equivalent. In particular, @code{AS_ESCAPE} cannot +generate @emph{single-quoted} strings (but @code{AS_QUOTE_S} can). + +@strong{Compatibility Note:} Prior to Autoconf 2.73, the various +possibilities for @var{chars} on each row of the table above were not +equivalent. The behavior of older @code{AS_ESCAPE} only matches the +behavior of @code{AS_QUOTE_D} for the first possibility listed in each +row. + +When @code{AS_ESCAPE} is used for its intended purpose of generating +double-quoted shell strings, it is a @emph{bug} to use any of the other +possibilities: they would cause it not to quote all @var{string}s +correctly. Therefore, in Autoconf 2.73 and later, @code{AS_ESCAPE} +treats all the possibilities on each row as equivalent to the first. +If you were using @code{AS_ESCAPE} off-label and really wanted the +old behavior of @samp{AS_ESCAPE([@var{string}], [`$])} (for example), +change it to @samp{m4_bpatsubst([@var{string}], [[`$]], [\\\&])}. +@end defmac + @node Autoconf 1 @section Upgrading From Version 1 @cindex Upgrading autoconf diff --git a/lib/autoconf/autoheader.m4 b/lib/autoconf/autoheader.m4 index 99c0cf5e..ba7d3f8e 100644 --- a/lib/autoconf/autoheader.m4 +++ b/lib/autoconf/autoheader.m4 @@ -46,7 +46,7 @@ m4_define([AH_OUTPUT], []) # Quote for Perl '' strings, which are those used by Autoheader. m4_define([AH_VERBATIM], [AS_LITERAL_WORD_IF([$1], - [AH_OUTPUT(_m4_expand([$1]), AS_ESCAPE([[$2]], [\']))])]) + [AH_OUTPUT(_m4_expand([$1]), m4_bpatsubst([[$2]], [[\']], [\\\&]))])]) # AH_TEMPLATE(KEY, DESCRIPTION) diff --git a/lib/autoconf/general.m4 b/lib/autoconf/general.m4 index f5ad64ba..2953191b 100644 --- a/lib/autoconf/general.m4 +++ b/lib/autoconf/general.m4 @@ -2359,7 +2359,7 @@ m4_define([AC_DEFINE], [_AC_DEFINE_Q([_$0], $@)]) # Append the pre-expanded STRING and a newline to confdefs.h, as if by # a quoted here-doc. m4_define([_AC_DEFINE], -[AS_ECHO(["AS_ESCAPE([[$1]])"]) >>confdefs.h]) +[AS_ECHO([AS_QUOTE_D([[$1]])]) >>confdefs.h]) # AC_DEFINE_UNQUOTED(VARIABLE, [VALUE], [DESCRIPTION]) @@ -2377,11 +2377,11 @@ m4_define([AC_DEFINE_UNQUOTED], [_AC_DEFINE_Q([_$0], $@)]) # avoid AS_ECHO if "#" is present to avoid confusing m4 with comments, # but quadrigraphs are fine in that case. m4_define([_AC_DEFINE_UNQUOTED], -[m4_if(m4_bregexp([$1], [#\|\\\|`\|\(\$\|@S|@\)\((|{|@{:@\)]), [-1], - [AS_ECHO(["AS_ESCAPE([$1], [""])"]) >>confdefs.h], +[m4_bmatch([$1], [#\|\\\|`\|\(\$\|@S|@\)\((|{|@{:@\)], [cat >>confdefs.h <<_ACEOF [$1] -_ACEOF])]) +_ACEOF], + [AS_ECHO([AS_QUOTE_D([$1])]) >>confdefs.h])]) # _AC_DEFINE_Q(MACRO, VARIABLE, [VALUE], [DESCRIPTION]) @@ -2738,7 +2738,7 @@ AC_DEFUN([AC_TRY_COMMAND], # ------------------- AC_DEFUN([AC_RUN_LOG], [_AC_RUN_LOG([$1], - [AS_ECHO(["$as_me:${as_lineno-$LINENO}: AS_ESCAPE([$1])"])])]) + [AS_ECHO(["$as_me:${as_lineno-$LINENO}: "AS_QUOTE_D([$1])])])]) @@ -3187,7 +3187,8 @@ dnl Initialize each $ac_[]_AC_LANG_ABBREV[]_undeclared_builtin_options once. [AC_REQUIRE([_AC_UNDECLARED_BUILTIN_]_AC_LANG_ABBREV)]dnl [AS_VAR_PUSHDEF([ac_Symbol], [ac_cv_have_decl_$1])]dnl [ac_fn_check_decl ]dnl -["$LINENO" "$1" "ac_Symbol" "AS_ESCAPE([AC_INCLUDES_DEFAULT([$4])], [""])" ]dnl +["$LINENO" "$1" "ac_Symbol" ]dnl +[AS_QUOTE_D([AC_INCLUDES_DEFAULT([$4])], [allow-commands,allow-vars]) ]dnl ["$ac_[]_AC_LANG_ABBREV[]_undeclared_builtin_options" "_AC_LANG_PREFIX[]FLAGS"] [AS_VAR_IF([ac_Symbol], [yes], [$2], [$3])]dnl [AS_VAR_POPDEF([ac_Symbol])]dnl @@ -3401,7 +3402,7 @@ AC_DEFUN([AC_COMPUTE_INT], be computed])], [_$0_BODY])]dnl [AS_IF([ac_fn_[]_AC_LANG_ABBREV[]_compute_int "$LINENO" "$2" "$1" ]dnl - ["AS_ESCAPE([$3], [""])"], + [AS_QUOTE_D([$3], [allow-commands,allow-vars])], [], [$4]) ])# AC_COMPUTE_INT diff --git a/lib/autoconf/headers.m4 b/lib/autoconf/headers.m4 index dec8538b..15fc643f 100644 --- a/lib/autoconf/headers.m4 +++ b/lib/autoconf/headers.m4 @@ -90,7 +90,8 @@ AC_DEFUN([_AC_CHECK_HEADER_COMPILE], [_AC_CHECK_HEADER_COMPILE_FN()]dnl [AS_VAR_PUSHDEF([ac_Header], [ac_cv_header_$1])]dnl [ac_fn_[]_AC_LANG_ABBREV[]_check_header_compile ]dnl -["$LINENO" "$1" "ac_Header" "AS_ESCAPE([AC_INCLUDES_DEFAULT([$4])], [""])" +["$LINENO" "$1" "ac_Header" ]dnl +[AS_QUOTE_D([AC_INCLUDES_DEFAULT([$4])], [allow-commands,allow-vars]) AS_VAR_IF([ac_Header], [yes], [$2], [$3]) AS_VAR_POPDEF([ac_Header])])# _AC_CHECK_HEADER_COMPILE diff --git a/lib/autoconf/types.m4 b/lib/autoconf/types.m4 index f231fcfc..ae66afff 100644 --- a/lib/autoconf/types.m4 +++ b/lib/autoconf/types.m4 @@ -155,7 +155,7 @@ AC_DEFUN([_AC_CHECK_TYPE_NEW], [$0_BODY])]dnl [AS_VAR_PUSHDEF([ac_Type], [ac_cv_type_$1])]dnl [ac_fn_[]_AC_LANG_ABBREV[]_check_type "$LINENO" "$1" "ac_Type" ]dnl -["AS_ESCAPE([AC_INCLUDES_DEFAULT([$4])], [""])" +[AS_QUOTE_D([AC_INCLUDES_DEFAULT([$4])], [allow-commands,allow-vars]) AS_VAR_IF([ac_Type], [yes], [$2], [$3]) AS_VAR_POPDEF([ac_Type])dnl ])# _AC_CHECK_TYPE_NEW @@ -944,7 +944,7 @@ AC_DEFUN([AC_CHECK_MEMBER], [AS_VAR_PUSHDEF([ac_Member], [ac_cv_member_$1])]dnl [ac_fn_[]_AC_LANG_ABBREV[]_check_member "$LINENO" ]dnl [m4_bpatsubst([$1], [^\([^.]*\)\.\(.*\)], ["\1" "\2"]) "ac_Member" ]dnl -["AS_ESCAPE([AC_INCLUDES_DEFAULT([$4])], [""])" +[AS_QUOTE_D([AC_INCLUDES_DEFAULT([$4])], [allow-commands,allow-vars]) AS_VAR_IF([ac_Member], [yes], [$2], [$3]) AS_VAR_POPDEF([ac_Member])dnl ])# AC_CHECK_MEMBER diff --git a/lib/autotest/general.m4 b/lib/autotest/general.m4 index a0668dde..4825bc60 100644 --- a/lib/autotest/general.m4 +++ b/lib/autotest/general.m4 @@ -153,7 +153,7 @@ m4_defn([_AT_LINE_base]):__line__]) # _AT_LINE_ESCAPED # ---------------- # Same as AT_LINE, but already escaped for the shell. -m4_define([_AT_LINE_ESCAPED], ["AS_ESCAPE(m4_dquote(AT_LINE))"]) +m4_define([_AT_LINE_ESCAPED], [AS_QUOTE_D([AT_LINE])]) # _AT_NORMALIZE_TEST_GROUP_NUMBER(SHELL-VAR) @@ -449,7 +449,7 @@ at_color=m4_ifdef([AT_color], [AT_color], [no]) # numerical order. at_format='m4_bpatsubst(m4_defn([AT_ordinal]), [.], [?])' # Description of all the test groups. -at_help_all="AS_ESCAPE(m4_dquote(m4_defn([AT_help_all])))" +at_help_all=AS_QUOTE_D(m4_defn([AT_help_all])) # List of the all the test groups. at_groups_all=`AS_ECHO(["$at_help_all"]) | sed 's/;.*//'` @@ -1891,7 +1891,7 @@ m4_define([AT_ordinal], m4_incr(AT_ordinal)) m4_divert_push([TEST_GROUPS])dnl [#AT_START_]AT_ordinal at_fn_group_banner AT_ordinal 'm4_defn([AT_line])' \ - "AS_ESCAPE(m4_dquote(m4_defn([AT_description])))" m4_format(["%*s"], + AS_QUOTE_D(m4_dquote(m4_defn([AT_description]))) m4_format(["%*s"], m4_max(0, m4_eval(47 - m4_qlen(m4_defn([AT_description])))), [])m4_if( AT_banner_ordinal, [0], [], [ AT_banner_ordinal]) m4_ifset([AT_prepare_each_test], [AT_prepare_each_test @@ -2001,7 +2001,7 @@ m4_define([AT_banner_ordinal], m4_incr(AT_banner_ordinal)) m4_divert_text([BANNERS], [@%:@ Banner AT_banner_ordinal. AT_LINE @%:@ Category starts at test group m4_incr(AT_ordinal). -at_banner_text_[]AT_banner_ordinal="AS_ESCAPE([$1])"])dnl +at_banner_text_[]AT_banner_ordinal=AS_QUOTE_D([$1])])dnl ])# AT_BANNER @@ -2076,17 +2076,21 @@ $2[]_ATEOF # This may cause spurious failures when the test suite is run with '-x'. # _AT_DEFINE_SETUP([AT_CHECK], -[_AT_CHECK(m4_expand([$1]), [$2], AS_ESCAPE(m4_dquote(m4_expand([$3]))), - AS_ESCAPE(m4_dquote(m4_expand([$4]))), [$5], [$6])]) +[_AT_CHECK(m4_expand([$1]), [$2], + AS_QUOTE_D([$3]), + AS_QUOTE_D([$4]), + [$5], [$6])]) # AT_CHECK_UNQUOTED(COMMANDS, [STATUS = 0], STDOUT, STDERR, # [RUN-IF-FAIL], [RUN-IF-PASS]) # --------------------------------------------------------- -# Like AT_CHECK, but do not AS_ESCAPE shell metacharacters in the STDOUT +# Like AT_CHECK, but perform string interpolations in the STDOUT # and STDERR arguments before running the comparison. _AT_DEFINE_SETUP([AT_CHECK_UNQUOTED], -[_AT_CHECK(m4_expand([$1]), [$2], AS_ESCAPE(m4_dquote(m4_expand([$3])), [""]), - AS_ESCAPE(m4_dquote(m4_expand([$4])), [""]), [$5], [$6])]) +[_AT_CHECK(m4_expand([$1]), [$2], + AS_QUOTE_D([$3], [allow-commands,allow-vars]), + AS_QUOTE_D([$4], [allow-commands,allow-vars]), + [$5], [$6])]) # AT_CHECK_NOESCAPE(COMMANDS, [STATUS = 0], STDOUT, STDERR, # [RUN-IF-FAIL], [RUN-IF-PASS]) @@ -2194,7 +2198,7 @@ dnl We know at build time that tracing COMMANDS is never safe. dnl We know at build time that tracing COMMANDS is always safe. [[at_fn_check_prepare_trace],]dnl dnl COMMANDS may contain parameter expansions; expand them at runtime. -[[at_fn_check_prepare_dynamic "AS_ESCAPE([[$1]], [`\"])"])[]]dnl +[[at_fn_check_prepare_dynamic AS_QUOTE_D([[$1]], [allow-vars])])[]]dnl [_m4_popdef([at_reason])]) @@ -2202,28 +2206,28 @@ dnl COMMANDS may contain parameter expansions; expand them at runtime. # ----------------------------- # These are subroutines of AT_CHECK. Using indirect dispatch is a tad # faster than using m4_case, and these are called very frequently. -m4_define([AT_DIFF_STDERR(stderr)], +m4_define([AT_DIFF_STDERR("stderr")], [echo stderr:; tee stderr <"$at_stderr"]) -m4_define([AT_DIFF_STDERR(stderr-nolog)], +m4_define([AT_DIFF_STDERR("stderr-nolog")], [echo stderr captured; cp "$at_stderr" stderr]) -m4_define([AT_DIFF_STDERR(ignore)], +m4_define([AT_DIFF_STDERR("ignore")], [echo stderr:; cat "$at_stderr"]) -m4_define([AT_DIFF_STDERR(ignore-nolog)]) -m4_define([AT_DIFF_STDERR(experr)], +m4_define([AT_DIFF_STDERR("ignore-nolog")]) +m4_define([AT_DIFF_STDERR("experr")], [$at_diff experr "$at_stderr" || at_failed=:]) -m4_define([AT_DIFF_STDERR()], +m4_define([AT_DIFF_STDERR("")], [at_fn_diff_devnull "$at_stderr" || at_failed=:]) -m4_define([AT_DIFF_STDOUT(stdout)], +m4_define([AT_DIFF_STDOUT("stdout")], [echo stdout:; tee stdout <"$at_stdout"]) -m4_define([AT_DIFF_STDOUT(stdout-nolog)], +m4_define([AT_DIFF_STDOUT("stdout-nolog")], [echo stdout captured; cp "$at_stdout" stdout]) -m4_define([AT_DIFF_STDOUT(ignore)], +m4_define([AT_DIFF_STDOUT("ignore")], [echo stdout:; cat "$at_stdout"]) -m4_define([AT_DIFF_STDOUT(ignore-nolog)]) -m4_define([AT_DIFF_STDOUT(expout)], +m4_define([AT_DIFF_STDOUT("ignore-nolog")]) +m4_define([AT_DIFF_STDOUT("expout")], [$at_diff expout "$at_stdout" || at_failed=:]) -m4_define([AT_DIFF_STDOUT()], +m4_define([AT_DIFF_STDOUT("")], [at_fn_diff_devnull "$at_stdout" || at_failed=:]) # _AT_CHECK(COMMANDS, [STATUS = 0], STDOUT, STDERR, @@ -2263,17 +2267,17 @@ m4_define([AT_DIFF_STDOUT()], m4_define([_AT_CHECK], [m4_define([AT_ingroup])]dnl [{ set +x -AS_ECHO(["$at_srcdir/AT_LINE: AS_ESCAPE([[$1]])"]) +AS_ECHO(["$at_srcdir/AT_LINE: "AS_QUOTE_D([[$1]])]) _AT_DECIDE_TRACEABLE([$1]) _AT_LINE_ESCAPED ( $at_check_trace; [$1] ) >>"$at_stdout" 2>>"$at_stderr" AS_MESSAGE_LOG_FD>&- at_status=$? at_failed=false $at_check_filter m4_ifdef([AT_DIFF_STDERR($4)], [m4_indir([AT_DIFF_STDERR($4)])], - [echo >>"$at_stderr"; AS_ECHO([["$4"]]) | \ + [echo >>"$at_stderr"; AS_ECHO([[$4]]) | \ $at_diff - "$at_stderr" || at_failed=:]) m4_ifdef([AT_DIFF_STDOUT($3)], [m4_indir([AT_DIFF_STDOUT($3)])], - [echo >>"$at_stdout"; AS_ECHO([["$3"]]) | \ + [echo >>"$at_stdout"; AS_ECHO([[$3]]) | \ $at_diff - "$at_stdout" || at_failed=:]) m4_if([$2], [ignore], [at_fn_check_skip], [at_fn_check_status m4_default([$2], [0])]) $at_status "$at_srcdir/AT_LINE" diff --git a/lib/m4sugar/m4sh.m4 b/lib/m4sugar/m4sh.m4 index ba6d5409..4538a3d4 100644 --- a/lib/m4sugar/m4sh.m4 +++ b/lib/m4sugar/m4sh.m4 @@ -222,7 +222,7 @@ dnl Remove any tests from suggested that are also required [m4_set_map([_AS_DETECT_SUGGESTED_BODY], [_AS_DETECT_SUGGESTED_PRUNE])]dnl [m4_pushdef([AS_EXIT], [exit m4_default(]m4_dquote([$][1])[, 1)])]dnl [if test "x$CONFIG_SHELL" = x; then - as_bourne_compatible="AS_ESCAPE(_m4_expand([_AS_BOURNE_COMPATIBLE]))" + as_bourne_compatible=AS_QUOTE_D([_AS_BOURNE_COMPATIBLE]) _AS_DETECT_EXPAND([as_required], [_AS_DETECT_REQUIRED_BODY]) _AS_DETECT_EXPAND([as_suggested], [_AS_DETECT_SUGGESTED_BODY]) AS_IF([_AS_RUN(["$as_required"])], @@ -763,69 +763,109 @@ m4_define([AS_MESSAGE_LOG_FD]) # shuffle fd's. m4_define([AS_ORIGINAL_STDIN_FD], [0]) - -# AS_ESCAPE(STRING, [CHARS = `\"$]) +# AS_QUOTE_S(TEXT) +# ---------------- +# Quote TEXT for the shell, using single quotes. Expand macros within +# the text prior to quotation. +# +# With GNU m4 1.4.19, avoiding a call to m4_bpatsubst when it would +# have nothing to do is good for a ~2.5x speedup. +m4_define([AS_QUOTE_S], ['_$0(m4_dquote(m4_expand([$1])))']) +m4_define([_AS_QUOTE_S], +[m4_if([m4_index([$1], ['])], [-1], + [$1], + [m4_bpatsubst([$1], ['], ['\''])])]) + +# AS_QUOTE_D(TEXT, [INTERPOLATION]) # --------------------------------- -# Add backslash escaping to the CHARS in STRING. In an effort to -# optimize use of this macro inside double-quoted shell constructs, -# the behavior is intentionally undefined if CHARS is longer than 4 -# bytes, or contains bytes outside of the set [`\"$]. However, -# repeated bytes within the set are permissible (AS_ESCAPE([$1], [""]) -# being a common way to be nice to syntax highlighting). -# -# Avoid the m4_bpatsubst if there are no interesting characters to escape. -# _AS_ESCAPE bypasses argument defaulting. +# Quote TEXT for the shell, using double quotes. Expand macros within +# the text prior to quotation. INTERPOLATION says what kinds of +# interpolations to allow: +# +# - blank/absent: no interpolation +# - allow-vars: allow interpolation of $var ${var} $((expr)) +# - allow-commands: allow interpolation of `command` $(command) +# - allow-commands,allow-vars +# allow-vars,allow-commands +# allow both the above kinds of interpolation +# (there is intentionally no 'allow-all' because we want +# people to be able to find all cases where command interpolation +# is enabled by grepping for 'allow-commands'). +m4_define([AS_QUOTE_D], +["_$0_QUOTE(m4_dquote(m4_expand([$1])), _$0_IARGCHECK([$2]))"]) + +# _AS_QUOTE_D_IARGCHECK(INTERPOLATION) +# ------------------------------------ +# Evaluate the INTERPOLATION argument to AS_QUOTE_D. If it is valid, +# return a list of the second, third, fourth, and fifth arguments to +# _AS_QUOTE_D_QUOTE, otherwise issue an error. +m4_define([_AS_QUOTE_D_ARGCHECK], +[m4_case(m4_translit([[$1]], [ ][ ][ +]), + [], [[$], [\"`], [$$$], [[\"$`]]], + [allow-vars], [[$], [\"`], [$$$], [[\"`]|\$(\($|[^(]\)]], + [allow-commands], [[$], [\"], [$$], [[\"]\|\$[a-zA-Z_{]\|\$((]], + [allow-commands,allow-vars], [["], [\], ["], [[\"]]], + [allow-vars,allow-commands], [["], [\], ["], [[\"]]], + [m4_fatal([invalid INTERPOLATION argument '$1' to AS_QUOTE_D])])]) + +# _AS_QUOTE_D_QUOTE(STRING, C, HARS, CCCC, REGEX) +# ----------------------------------------------- +# Insert a backslash in STRING before every match for REGEX. +# C, HARS, and CCCC are hints for a performance hack: if C is not +# present in STRING after transliterating HARS to CCCC, then +# REGEX is assumed not to match anywhere within STRING. +# (As noted for AS_QUOTE_S, avoiding calls to m4_bpatsubst is good +# for a ~2.5x speedup even when the overhead of the translit and index +# are accounted for. +m4_define([_AS_QUOTE_D_QUOTE], +[m4_if(m4_index(m4_if([$3], [], [$1], [m4_translit([[$1]], [$3], [$4])]), [$2]), + [-1], + [$1], + [m4_bpatsubst([$1], [$5], [\\\&])])]) + +# AS_ESCAPE(STRING, [CHARS]) +# -------------------------- +# Superseded by AS_QUOTE_D. The differences are: +# - Does not wrap " ... " around the result. +# - Does not pre-expand the string. +# - CHARS is a much clumsier way to specify what interpolation to allow. +# Almost all existing uses of AS_ESCAPE with a non-default CHARS +# argument are incorrect per the original implementation. +# This compat stub attempts to map each possible value of CHARS +# (plus one more that wasn't supposed to happen at all) to an +# AS_QUOTE_D/S invocation that will do what was intended. m4_define([AS_ESCAPE], -[_$0([$1], m4_if([$2], [], [[`], [\"$]], [m4_substr([$2], [0], [1]), [$2]]))]) - -# _AS_ESCAPE(STRING, KEY, SET) -# ---------------------------- -# Backslash-escape all instances of the single byte KEY or up to four -# bytes in SET occurring in STRING. Although a character can occur -# multiple times, optimum efficiency occurs when KEY and SET are -# distinct, and when SET does not exceed two bytes. These particular -# semantics allow for the fewest number of parses of STRING, as well -# as taking advantage of the optimizations in m4 1.4.13+ when -# m4_translit is passed SET of size 2 or smaller. -m4_define([_AS_ESCAPE], -[m4_if(m4_index(m4_translit([[$1]], [$3], [$2$2$2$2]), [$2]), [-1], - [$0_], [m4_bpatsubst])([$1], [[$2$3]], [\\\&])]) -m4_define([_AS_ESCAPE_], [$1]) - - -# _AS_QUOTE(STRING) -# ----------------- -# If there are quoted (via backslash) backquotes, output STRING -# literally and warn; otherwise, output STRING with ` and " quoted. -# -# Compatibility glue between the old AS_MSG suite which did not -# quote anything, and the modern suite which quotes the quotes. -# If STRING contains '\\' or '\$', it's modern. -# If STRING contains '\"' or '\`', it's old. -# Otherwise it's modern. -# -# Profiling shows that m4_index is 5 to 8x faster than m4_bregexp. The -# slower implementation used: -# m4_bmatch([$1], -# [\\[\\$]], [$2], -# [\\[`"]], [$3], -# [$2]) -# The current implementation caters to the common case of no backslashes, -# to minimize m4_index expansions (hence the nested if). -m4_define([_AS_QUOTE], -[m4_cond([m4_index([$1], [\])], [-1], [_AS_QUOTE_MODERN], - [m4_eval(m4_index(m4_translit([[$1]], [$], [\]), [\\]) >= 0)], -[1], [_AS_QUOTE_MODERN], - [m4_eval(m4_index(m4_translit([[$1]], ["], [`]), [\`]) >= 0)],dnl" -[1], [_AS_QUOTE_OLD], - [_AS_QUOTE_MODERN])([$1])]) - -m4_define([_AS_QUOTE_MODERN], -[_AS_ESCAPE([$1], [`], [""])]) - -m4_define([_AS_QUOTE_OLD], -[m4_warn([obsolete], - [back quotes and double quotes must not be escaped in: $1])$1]) +[m4_warn([obsolete], [AS_ESCAPE is deprecated, change to AS_QUOTE_D or _S])]dnl +dnl Handle the most common case without going through _AS_ESCAPE_MAPCHARS, +dnl which is expensive. +dnl AS_ESCAPE was never intended to produce single-quoted strings but +dnl someone tried to use it that way anyway! +dnl https://sources.debian.org/src/fakechroot/2.20.1+ds-17/m4/check_func_argtypes.m4/?hl=111#L111 +[m4_case([$2], + [], [_AS_QUOTE_D_QUOTE([$1], _AS_QUOTE_D_ARGCHECK([]))], + ['], [_AS_QUOTE_S([$1])], + [''], [_AS_QUOTE_S([$1])], + [_AS_QUOTE_D_QUOTE([$1], _AS_QUOTE_D_ARGCHECK([]))])]) +# [_AS_QUOTE_D_QUOTE([$1], _AS_QUOTE_D_IARGCHECK(_AS_ESCAPE_MAPCHARS([$2])))])]) + +# The characters could be in any order and they could also be +# duplicated. M4 + M4sugar does have a "remove duplicate from list" +# mechanism, but it doesn't have "sort list" at all. +# m4_define([_AS_ESCAPE_MAPCHARS], +# [__AS_ESCAPE_MAPCHARS(m4_join_uniq([], m4_bpatsubst([$1], [.], [[\&],])))]) +# m4_define([__AS_ESCAPE_MAPCHARS], +# [[[$1]] m4_bmatch([$1], +# [^\([$`][$`]\|[$`"][$`"][$`"]\|[$\`][$\`][$\`]\|[$`\"][$`\"][$`\"][$`\"]\)$], +# [], +# [^\([$]\|[\$][\$]\|["$]["$]\|[\"$][\"$][\"$]\)$], +# [allow-commands], +# [^\([`]\|[\`][\`]\|["`]["`]\|[\"`][\"`][\"`]\)$], +# [allow-vars], +# [^\([\\]|["]|[\"][\"]\)$], +# [allow-commands,allow-vars], +# [invalid])]) +# [m4_fatal([invalid CHARS argument '$1' to AS_ESCAPE])])]) # _AS_ECHO_UNQUOTED(STRING, [FD = AS_MESSAGE_FD]) @@ -837,9 +877,17 @@ m4_define([_AS_ECHO_UNQUOTED], # _AS_ECHO(STRING, [FD = AS_MESSAGE_FD]) # -------------------------------------- -# Protect STRING from backquote expansion, echo the result to FD. +# Protect STRING from backquote expansion but not variable expansion, +# echo the result to FD. m4_define([_AS_ECHO], -[_AS_ECHO_UNQUOTED([_AS_QUOTE([$1])], [$2])]) +[AS_ECHO([AS_QUOTE_D([$1], [allow-vars])]) >&m4_default([$2], [AS_MESSAGE_FD])]) + + +# _AS_ECHO_N(STRING, [FD = AS_MESSAGE_FD]) +# ---------------------------------------- +# Same as _AS_ECHO, but echo doesn't return to a new line. +m4_define([_AS_ECHO_N], +[AS_ECHO_N([AS_QUOTE_D([$1], [allow-vars])]) >&m4_default([$2], [AS_MESSAGE_FD])]) # _AS_ECHO_LOG(STRING) @@ -850,13 +898,6 @@ m4_defun_init([_AS_ECHO_LOG], [_AS_ECHO([$as_me:${as_lineno-$LINENO}: $1], AS_MESSAGE_LOG_FD)]) -# _AS_ECHO_N(STRING, [FD = AS_MESSAGE_FD]) -# ---------------------------------------- -# Same as _AS_ECHO, but echo doesn't return to a new line. -m4_define([_AS_ECHO_N], -[AS_ECHO_N(["_AS_QUOTE([$1])"]) >&m4_default([$2], [AS_MESSAGE_FD])]) - - # AS_MESSAGE(STRING, [FD = AS_MESSAGE_FD]) # ---------------------------------------- # Output "`basename $0`: STRING" to the open file FD, and if logging @@ -913,7 +954,7 @@ _m4_popdef([AS_MESSAGE_LOG_FD])dnl m4_defun_init([AS_ERROR], [m4_append_uniq([_AS_CLEANUP], [m4_divert_text([M4SH-INIT-FN], [_AS_ERROR_PREPARE[]])])], -[as_fn_error m4_default([$2], [$?]) "_AS_QUOTE([$1])"m4_ifval(AS_MESSAGE_LOG_FD, +[as_fn_error m4_default([$2], [$?]) AS_QUOTE_D([$1], [allow-vars])m4_ifval(AS_MESSAGE_LOG_FD, [ "$LINENO" AS_MESSAGE_LOG_FD])]) @@ -1462,8 +1503,7 @@ m4_define([_AS_BOX], # _AS_BOX_LITERAL(MESSAGE, [FRAME-CHARACTER = '-']) # ------------------------------------------------- m4_define([_AS_BOX_LITERAL], -[AS_ECHO(["_AS_ESCAPE(m4_dquote(m4_expand([m4_text_box($@)])), [`], [\"$])"])]) - +[AS_ECHO([AS_QUOTE_D([m4_text_box($@)])])]) # _AS_BOX_INDIR(MESSAGE, [FRAME-CHARACTER = '-']) # ----------------------------------------------- @@ -1886,7 +1926,7 @@ m4_define([_AS_TR_SH_LITERAL], [pp[]]]m4_dquote(m4_for(,1,255,,[[_]]))[)]) m4_define([_AS_TR_SH_INDIR], -[`AS_ECHO(["_AS_ESCAPE([[$1]], [`], [\])"]) | sed "$as_sed_sh"`]) +[`AS_ECHO([AS_QUOTE_D([$1], [allow-vars])]) | sed "$as_sed_sh"`]) # _AS_TR_CPP_PREPARE @@ -1920,7 +1960,7 @@ m4_define([_AS_TR_CPP_LITERAL], [P[]]]m4_dquote(m4_defn([m4_cr_LETTERS])m4_for(,1,255,,[[_]]))[)]) m4_define([_AS_TR_CPP_INDIR], -[`AS_ECHO(["_AS_ESCAPE([[$1]], [`], [\])"]) | sed "$as_sed_cpp"`]) +[`AS_ECHO([AS_QUOTE_D([$1], [allow-vars])]) | sed "$as_sed_cpp"`]) # _AS_TR_PREPARE @@ -1959,7 +1999,7 @@ m4_defun([_AS_VAR_APPEND_PREPARE], VAR. Take advantage of any shell optimizations that allow amortized linear growth over repeated appends, instead of the typical quadratic growth present in naive implementations.]) -AS_IF([_AS_RUN(["AS_ESCAPE(m4_quote(_AS_VAR_APPEND_WORKS))"])], +AS_IF([_AS_RUN([AS_QUOTE_D([_AS_VAR_APPEND_WORKS])])], [eval 'as_fn_append () { eval $[]1+=\$[]2 @@ -2000,7 +2040,7 @@ m4_defun([_AS_VAR_ARITH_PREPARE], [Perform arithmetic evaluation on the ARGs, and store the result in the global $as_val. Take advantage of shells that can avoid forks. The arguments must be portable across $(()) and expr.]) -AS_IF([_AS_RUN(["AS_ESCAPE(m4_quote(_AS_VAR_ARITH_WORKS))"])], +AS_IF([_AS_RUN([AS_QUOTE_D([_AS_VAR_ARITH_WORKS])])], [eval 'as_fn_arith () { as_val=$(( $[]* )) @@ -2048,7 +2088,7 @@ m4_define([AS_VAR_COPY], m4_define([AS_VAR_GET], [AS_LITERAL_WORD_IF([$1], [$$1], - [`eval 'as_val=${'_AS_ESCAPE([[$1]], [`], [\])'};AS_ECHO(["$as_val"])'`])]) + [`eval 'as_val=\$$2;AS_ECHO(["$as_val"])'`])]) # AS_VAR_IF(VARIABLE, VALUE, IF-TRUE, IF-FALSE) @@ -2061,7 +2101,7 @@ m4_define([AS_VAR_IF], [AS_VAR_COPY([as_val], [$1]) AS_IF(m4_ifval([$2], [[test "x$as_val" = x[]$2]], [[${as_val:+false} :]])], [AS_IF(m4_ifval([$2], - [[eval test \"x\$"$1"\" = x"_AS_ESCAPE([$2], [`], [\"$])"]], + [[eval test \"x\$"$1"\" = x[]AS_QUOTE_D([$2])]], [[eval \${$1:+false} :]])]), [$3], [$4])]) @@ -2128,7 +2168,7 @@ m4_pushdef([$1], [$as_[$1]])], m4_define([AS_VAR_SET], [AS_LITERAL_WORD_IF([$1], [$1=$2], - [eval "$1=_AS_ESCAPE([$2], [`], [\"$])"])]) + [eval "$1="AS_QUOTE_D([$2])])]) # AS_VAR_SET_IF(VARIABLE, IF-TRUE, IF-FALSE)