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
<http://codesearch.debian.net/search?q=%5CbAS_ESCAPE%5Cb+-pkg%3Aautoconf+-pkg%3Aautoconf2.69&literal=0>.)
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 a500de3..c806286 100644
--- a/doc/autoconf.texi
+++ b/doc/autoconf.texi
@@ -13959,6 +13959,7 @@
@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 @@
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 @@
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 @@
@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 @@
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 99c0cf5..ba7d3f8 100644
--- a/lib/autoconf/autoheader.m4
+++ b/lib/autoconf/autoheader.m4
@@ -46,7 +46,7 @@
# 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 f5ad64b..2953191 100644
--- a/lib/autoconf/general.m4
+++ b/lib/autoconf/general.m4
@@ -2359,7 +2359,7 @@
# 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 @@
# 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_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 @@
[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 @@
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 dec8538..15fc643 100644
--- a/lib/autoconf/headers.m4
+++ b/lib/autoconf/headers.m4
@@ -90,7 +90,8 @@
[_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 f231fcf..ae66aff 100644
--- a/lib/autoconf/types.m4
+++ b/lib/autoconf/types.m4
@@ -155,7 +155,7 @@
[$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 @@
[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 a0668dd..4825bc6 100644
--- a/lib/autotest/general.m4
+++ b/lib/autotest/general.m4
@@ -153,7 +153,7 @@
# _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 @@
# 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_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_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 @@
# 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 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 @@
# -----------------------------
# 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_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 ba6d540..4538a3d 100644
--- a/lib/m4sugar/m4sh.m4
+++ b/lib/m4sugar/m4sh.m4
@@ -222,7 +222,7 @@
[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 @@
# shuffle fd's.
m4_define([AS_ORIGINAL_STDIN_FD], [0])
+# 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_ESCAPE(STRING, [CHARS = `\"$])
+# 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).
+# Quote TEXT for the shell, using double quotes. Expand macros within
+# the text prior to quotation. INTERPOLATION says what kinds of
+# interpolations to allow:
#
-# Avoid the m4_bpatsubst if there are no interesting characters to escape.
-# _AS_ESCAPE bypasses argument defaulting.
+# - 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]]))])
+[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])))])])
-# _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])
+# 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 @@
# _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 @@
[_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_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 @@
# _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 @@
[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 @@
[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 @@
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 @@
[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_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 @@
[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_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)