]> git.ipfire.org Git - thirdparty/autoconf.git/commitdiff
AS_IF: Handle else clause being empty after macro expansion (#110369)
authorZack Weinberg <zackw@panix.com>
Sun, 15 Nov 2020 18:56:18 +0000 (13:56 -0500)
committerZack Weinberg <zackw@panix.com>
Sun, 15 Nov 2020 19:16:16 +0000 (14:16 -0500)
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.

NEWS
doc/autoconf.texi
lib/m4sugar/m4sh.m4
tests/m4sh.at

diff --git a/NEWS b/NEWS
index 5994d6be94e7a332ae13b9d4a6bccfb418634ede..4cad963b1e09552f8904b803a5ab0d45677e70fa 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -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_IF’s 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.
 
index da050b475690b421483d639c05f7bcc5fdccfacc..f78e5fc531c3360accf549d9267b0ba69ef62e44 100644 (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}).
index 8f96eb7bccd07402def3fb225439dedd555ac1a4..81b3f82e7efe6d09c47ade54254a71a4bb05706a 100644 (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)
index 633becdd1a628f57e161e1b879284464dcff95d1..21c6123759cf8d4eb9f5c3e9dcfec7a078b16564 100644 (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, [],