AS_IF: Handle else clause being empty after macro expansion (#110369)

AS_IF can emit a syntactically invalid shell if-then-else,

  if CONDITION
  then :
    # ...
  else
  fi

when its IF-FALSE argument consists of macros that don’t produce any
shell code.  This was a documented limitation in AS_IF, but it’s a bad
limitation to have, because macros that *used* to expand to shell
commands might start expanding to nothing in future releases.  For
instance, this broke the libzmq configure script, which did

  AC_PROG_CC
  AX_CHECK_COMPILE_FLAG([-std=gnu11],
    [CFLAGS+=" -std=gnu11"],
    [AC_PROG_CC_C99])

Perfectly valid in 2.69, but in 2.70 AC_PROG_CC_C99 doesn’t produce
any shell code and the script crashes.

We had that limitation for good reason: we can’t just put ‘:’ at the
beginning of the else-clause, like we do for the then-clause, because
that would clobber $? and the IF-FALSE commands might want to inspect
it.  (This doesn’t matter for the then-clause, because $? is always
zero at the beginning of a then-clause anyway.)  The simplest and
least inefficient shell construct I can find that works in this
context is a shell function that does ‘return $?’.  Due to awkward
M4sh initialization ordering constraints (AS_IF gets used before we
can safely use shell functions) an indirection through a shell
variable is necessary.  The structure of a m4sh script is now

  #! /bin/sh
  ## M4sh Initialization
  as_nop=:

  ...
  ## M4sh Shell Functions

  as_fn_nop () { return $?; }
  as_nop=as_fn_nop
  ...

and AS_IF emits

  if CONDITION
  then :
    # ...
  else $as_nop
    # ...
  fi

The uses of AS_IF that appear before the beginning of the M4sh Shell
Functions section are all under our control and they don’t need to
look at $?.

If anyone has a better idea for how to make this work I will be glad
to hear it.

Fixes bug #110369.

* lib/m4sugar/m4sh.m4
  (_AS_IF_ELSE): When $1 is nonempty, invoke _AS_EMPTY_ELSE_PREPARE.
  Emit $as_nop at beginning of else clause.
  (_AS_BOURNE_COMPATIBLE): Initialize as_nop to ‘:’.
  (_AS_EMPTY_ELSE_PREPARE): New macro which emits a definition of
  as_fn_nop and resets as_nop to as_fn_nop.
  (AS_PREPARE, _AS_PREPARE): Invoke _AS_EMPTY_ELSE_PREPARE.
  (_AS_UNSET_PREPARE): Tweak white space.

* tests/m4sh.at (AS_IF and AS_CASE): Test AS_IF’s IF-FALSE argument
  being empty after macro expansion.

* doc/autoconf.texi (AS_IF): Remove warning about use with
  ‘run-if-false’ argument empty after macro expansion.
This commit is contained in:
Zack Weinberg 2020-11-15 13:56:18 -05:00
parent 996f608165
commit fd633e92cb
No known key found for this signature in database
GPG Key ID: 384F8E68AC65B0D5
4 changed files with 41 additions and 17 deletions

6
NEWS
View File

@ -387,6 +387,12 @@ GNU Autoconf NEWS - User visible changes.
This means configure scripts will no longer check repeatedly for the
C compiler under some combinations of macro use.
*** AS_IFs if-false argument may be empty after macro expansion.
This long-standing limitation broke configure scripts that used
macros in this position that emitted shell code in 2.69 but no
longer do, so we have lifted it.
*** AC_HEADER_MAJOR detects the location of the major, minor, and
makedev macros correctly under glibc 2.25 and later.

View File

@ -13972,13 +13972,6 @@ AS_IF([test "x$foo" = xyes], [HANDLE_FOO([yes])],
ensures any required macros of @code{HANDLE_FOO}
are expanded before the first test.
The @var{run-if-false} argument should either consist entirely of
blanks, or expand to a nonempty shell command. For example,
@code{AS_IF([:], [:], [[]])} is invalid because its @var{run-if-false}
argument contains the nonblank characters @code{[]} which expand to
nothing. This restriction on @var{run-if-false} also applies to other
macros with ``if-false'' arguments denoting shell commands.
This macro should be used instead of plain @samp{if} in code
outside of an @code{AC_DEFUN} macro, when the contents of the @samp{if}
use @code{AC_REQUIRE} directly or indirectly (@pxref{Prerequisite Macros}).

View File

@ -98,9 +98,10 @@ _$0
# _AS_BOURNE_COMPATIBLE
# ---------------------
# This is the part of AS_BOURNE_COMPATIBLE which has to be repeated inside
# each instance.
# each instance. See _AS_EMPTY_ELSE_PREPARE for explanation of as_nop.
m4_define([_AS_BOURNE_COMPATIBLE],
[AS_IF([test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1],
[as_nop=:
AS_IF([test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1],
[emulate sh
NULLCMD=:
[#] Pre-4.2 versions of Zsh do word splitting on ${1+"$[@]"}, which
@ -351,7 +352,8 @@ m4_defun([_AS_PREPARE],
[m4_pushdef([AS_MESSAGE_LOG_FD], [-1])]dnl
[_AS_ERROR_PREPARE
_m4_popdef([AS_MESSAGE_LOG_FD])]dnl
[_AS_EXIT_PREPARE
[_AS_EMPTY_ELSE_PREPARE
_AS_EXIT_PREPARE
_AS_UNSET_PREPARE
_AS_VAR_APPEND_PREPARE
_AS_VAR_ARITH_PREPARE
@ -387,6 +389,7 @@ AS_REQUIRE([_AS_CR_PREPARE])
AS_REQUIRE([_AS_LINENO_PREPARE])
AS_REQUIRE([_AS_ECHO_N_PREPARE])
AS_REQUIRE([_AS_EXIT_PREPARE])
AS_REQUIRE([_AS_EMPTY_ELSE_PREPARE])
AS_REQUIRE([_AS_LN_S_PREPARE])
AS_REQUIRE([_AS_MKDIR_P_PREPARE])
AS_REQUIRE([_AS_TEST_PREPARE])
@ -669,14 +672,27 @@ done[]_m4_popdef([$1])])
# | fi
# with simplifications when IF-TRUE1 and/or IF-FALSE are empty.
#
# Note: IF-TRUEn and IF_FALSE may be nonempty but, after further macro
# expansion, leave no actual shell code. We can't detect this, so we
# include a no-op statement in each clause to prevent it becoming a shell
# syntax error. For the IF-TRUEn this can simply be `:' at the beginning of
# the clause. IF-FALSE is harder because it must preserve the value of $?
# from the conditional expression. The most practical way to do this is
# with a shell function whose body is `return $?' but AS_IF is used before
# it's safe to use shell functions. To deal with *that*, there is a shell
# variable $as_fn_nop that expands to `:' before the nop shell function is
# defined, and invokes the nop shell function afterward. Early uses of
# AS_IF (which are all under our control) must not use the value of $? from
# the conditional expression in an else clause.
m4_define([_AS_IF],
[elif $1
then :
$2
])
m4_define([_AS_IF_ELSE],
m4_defun([_AS_IF_ELSE],
[m4_ifnblank([$1],
[else
[m4_append_uniq([_AS_CLEANUP], [AS_REQUIRE([_AS_EMPTY_ELSE_PREPARE])])]dnl
[else $as_nop
$1
])])
@ -687,6 +703,16 @@ then :
m4_map_args_pair([_$0], [_$0_ELSE], m4_shift2($@))]dnl
[fi[]])# AS_IF
m4_defun([_AS_EMPTY_ELSE_PREPARE],
[m4_divert_text([M4SH-INIT-FN],
[AS_FUNCTION_DESCRIBE([as_fn_nop], [],
[Do nothing but, unlike ":", preserve the value of $][?.])
as_fn_nop ()
{
return $[]?
}
as_nop=as_fn_nop])])
# AS_SET_STATUS(STATUS)
# ---------------------
@ -705,7 +731,8 @@ as_fn_unset ()
{
AS_UNSET([$[1]])
}
as_unset=as_fn_unset])
as_unset=as_fn_unset
])
# AS_UNSET(VAR)

View File

@ -1320,13 +1320,11 @@ dnl Handle blank arguments.
AS_IF([false], [:], [ ]) && AS_CASE([foo], [foo], []
) && echo seventeen
m4_define([empty])AS_IF([:], [empty]
) && AS_CASE([foo], [foo], [empty]) && echo eighteen
) && AS_IF([false], [], [empty]
) || AS_CASE([foo], [foo], [empty]) && echo eighteen
dnl Allow for users that don't know to avoid trailing whitespace
AS_IF([:
], [echo nineteen])
dnl We can't handle AS_IF([false], [:], [empty]) unless m4_expand is
dnl taught how to handle m4_require. The user is responsible for
dnl avoiding the syntax error in that case.
# check that require works correctly
m4_for([n], 1, 9, [],