Home | History | Annotate | Line # | Download | only in unit-tests
cond-short.mk revision 1.23.2.1
      1  1.23.2.1  perseant # $NetBSD: cond-short.mk,v 1.23.2.1 2025/08/02 05:58:32 perseant Exp $
      2       1.1    rillig #
      3       1.1    rillig # Demonstrates that in conditions, the right-hand side of an && or ||
      4       1.2    rillig # is only evaluated if it can actually influence the result.
      5      1.12    rillig # This is called 'short-circuit evaluation' and is the usual evaluation
      6      1.12    rillig # mode in most programming languages.  A notable exception is Ada, which
      7      1.12    rillig # distinguishes between the operators 'And', 'And Then', 'Or', 'Or Else'.
      8       1.1    rillig #
      9      1.13    rillig # Before 2020-06-28, the right-hand side of an && or || operator was always
     10      1.13    rillig # evaluated, which was wrong.  In cond.c 1.69 and var.c 1.197 on 2015-10-11,
     11      1.13    rillig # Var_Parse got a new parameter named 'wantit'.  Since then it would have been
     12      1.22    rillig # possible to skip evaluation of irrelevant expressions and only
     13      1.13    rillig # parse them.  They were still evaluated though, the only difference to
     14      1.23    rillig # relevant expressions was that in the irrelevant
     15      1.17    rillig # expressions, undefined variables were allowed.  This allowed for conditions
     16      1.17    rillig # like 'defined(VAR) && ${VAR:S,from,to,} != ""', which no longer produced an
     17      1.19    rillig # error message 'Malformed conditional', but the irrelevant expression was
     18      1.19    rillig # still evaluated.
     19      1.17    rillig #
     20      1.17    rillig # Since the initial commit on 1993-03-21, the manual page has been saying that
     21      1.17    rillig # make 'will only evaluate a conditional as far as is necessary to determine',
     22      1.17    rillig # but that was wrong.  The code in cond.c 1.1 from 1993-03-21 looks good since
     23      1.17    rillig # it calls Var_Parse(condExpr, VAR_CMD, doEval,&varSpecLen,&doFree), but the
     24      1.19    rillig # definition of Var_Parse did not call the third parameter 'doEval', as would
     25      1.17    rillig # be expected, but instead 'err', accompanied by the comment 'TRUE if
     26      1.17    rillig # undefined variables are an error'.  This subtle difference between 'do not
     27      1.17    rillig # evaluate at all' and 'allow undefined variables' led to the unexpected
     28      1.17    rillig # evaluation.
     29      1.16    rillig #
     30      1.16    rillig # See also:
     31      1.16    rillig #	var-eval-short.mk, for short-circuited variable modifiers
     32       1.2    rillig 
     33      1.16    rillig # The && operator:
     34       1.1    rillig 
     35       1.1    rillig .if 0 && ${echo "unexpected and" 1>&2 :L:sh}
     36       1.1    rillig .endif
     37       1.1    rillig 
     38       1.1    rillig .if 1 && ${echo "expected and" 1>&2 :L:sh}
     39       1.1    rillig .endif
     40       1.1    rillig 
     41       1.2    rillig .if 0 && exists(nonexistent${echo "unexpected and exists" 1>&2 :L:sh})
     42       1.2    rillig .endif
     43       1.2    rillig 
     44       1.2    rillig .if 1 && exists(nonexistent${echo "expected and exists" 1>&2 :L:sh})
     45       1.2    rillig .endif
     46       1.2    rillig 
     47       1.2    rillig .if 0 && empty(${echo "unexpected and empty" 1>&2 :L:sh})
     48       1.2    rillig .endif
     49       1.2    rillig 
     50       1.2    rillig .if 1 && empty(${echo "expected and empty" 1>&2 :L:sh})
     51       1.2    rillig .endif
     52       1.2    rillig 
     53       1.4    rillig # "VAR U11" is not evaluated; it was evaluated before 2020-07-02.
     54       1.4    rillig # The whole !empty condition is only parsed and then discarded.
     55       1.3    rillig VAR=	${VAR${:U11${echo "unexpected VAR U11" 1>&2 :L:sh}}}
     56       1.3    rillig VAR13=	${VAR${:U12${echo "unexpected VAR13" 1>&2 :L:sh}}}
     57       1.3    rillig .if 0 && !empty(VAR${:U13${echo "unexpected U13 condition" 1>&2 :L:sh}})
     58       1.3    rillig .endif
     59       1.3    rillig 
     60       1.3    rillig VAR=	${VAR${:U21${echo "unexpected VAR U21" 1>&2 :L:sh}}}
     61       1.3    rillig VAR23=	${VAR${:U22${echo   "expected VAR23" 1>&2 :L:sh}}}
     62       1.3    rillig .if 1 && !empty(VAR${:U23${echo   "expected U23 condition" 1>&2 :L:sh}})
     63       1.3    rillig .endif
     64       1.5    rillig VAR=	# empty again, for the following tests
     65       1.3    rillig 
     66       1.5    rillig # The :M modifier is only parsed, not evaluated.
     67       1.5    rillig # Before 2020-07-02, it was wrongly evaluated.
     68       1.4    rillig .if 0 && !empty(VAR:M${:U${echo "unexpected M pattern" 1>&2 :L:sh}})
     69       1.4    rillig .endif
     70       1.4    rillig 
     71       1.5    rillig .if 1 && !empty(VAR:M${:U${echo   "expected M pattern" 1>&2 :L:sh}})
     72       1.5    rillig .endif
     73       1.5    rillig 
     74       1.6    rillig .if 0 && !empty(VAR:S,from,${:U${echo "unexpected S modifier" 1>&2 :L:sh}},)
     75       1.6    rillig .endif
     76       1.6    rillig 
     77       1.6    rillig .if 0 && !empty(VAR:C,from,${:U${echo "unexpected C modifier" 1>&2 :L:sh}},)
     78       1.6    rillig .endif
     79       1.6    rillig 
     80       1.6    rillig .if 0 && !empty("" == "" :? ${:U${echo "unexpected ? modifier" 1>&2 :L:sh}} :)
     81       1.6    rillig .endif
     82       1.6    rillig 
     83       1.6    rillig .if 0 && !empty(VAR:old=${:U${echo "unexpected = modifier" 1>&2 :L:sh}})
     84       1.6    rillig .endif
     85       1.6    rillig 
     86       1.6    rillig .if 0 && !empty(1 2 3:L:@var@${:U${echo "unexpected @ modifier" 1>&2 :L:sh}}@)
     87       1.6    rillig .endif
     88       1.6    rillig 
     89       1.6    rillig .if 0 && !empty(:U${:!echo "unexpected exclam modifier" 1>&2 !})
     90       1.6    rillig .endif
     91       1.6    rillig 
     92       1.8    rillig # Irrelevant assignment modifiers are skipped as well.
     93       1.8    rillig .if 0 && ${1 2 3:L:@i@${FIRST::?=$i}@}
     94       1.8    rillig .endif
     95       1.8    rillig .if 0 && ${1 2 3:L:@i@${LAST::=$i}@}
     96       1.8    rillig .endif
     97       1.8    rillig .if 0 && ${1 2 3:L:@i@${APPENDED::+=$i}@}
     98       1.8    rillig .endif
     99       1.8    rillig .if 0 && ${echo.1 echo.2 echo.3:L:@i@${RAN::!=${i:C,.*,&; & 1>\&2,:S,., ,g}}@}
    100       1.8    rillig .endif
    101       1.8    rillig .if defined(FIRST) || defined(LAST) || defined(APPENDED) || defined(RAN)
    102      1.10    rillig .  warning first=${FIRST} last=${LAST} appended=${APPENDED} ran=${RAN}
    103       1.8    rillig .endif
    104       1.8    rillig 
    105      1.16    rillig # The || operator:
    106       1.2    rillig 
    107       1.1    rillig .if 1 || ${echo "unexpected or" 1>&2 :L:sh}
    108       1.1    rillig .endif
    109       1.1    rillig 
    110       1.1    rillig .if 0 || ${echo "expected or" 1>&2 :L:sh}
    111       1.1    rillig .endif
    112       1.1    rillig 
    113       1.2    rillig .if 1 || exists(nonexistent${echo "unexpected or exists" 1>&2 :L:sh})
    114       1.2    rillig .endif
    115       1.2    rillig 
    116       1.2    rillig .if 0 || exists(nonexistent${echo "expected or exists" 1>&2 :L:sh})
    117       1.2    rillig .endif
    118       1.2    rillig 
    119       1.2    rillig .if 1 || empty(${echo "unexpected or empty" 1>&2 :L:sh})
    120       1.2    rillig .endif
    121       1.2    rillig 
    122       1.2    rillig .if 0 || empty(${echo "expected or empty" 1>&2 :L:sh})
    123       1.2    rillig .endif
    124       1.2    rillig 
    125      1.19    rillig # Unreachable nested conditions are skipped completely as well.  These skipped
    126      1.19    rillig # lines may even contain syntax errors.  This allows to skip syntactically
    127      1.19    rillig # incompatible new features in older versions of make.
    128       1.1    rillig 
    129       1.1    rillig .if 0
    130       1.1    rillig .  if ${echo "unexpected nested and" 1>&2 :L:sh}
    131       1.1    rillig .  endif
    132       1.1    rillig .endif
    133       1.1    rillig 
    134       1.1    rillig .if 1
    135       1.1    rillig .elif ${echo "unexpected nested or" 1>&2 :L:sh}
    136       1.1    rillig .endif
    137       1.1    rillig 
    138       1.7       sjg 
    139      1.20    rillig NUMBER=		42
    140      1.20    rillig INDIR_NUMBER=	${NUMBER}
    141      1.20    rillig INDIR_UNDEF=	${UNDEF}
    142       1.7       sjg 
    143      1.20    rillig .if defined(NUMBER) && ${NUMBER} > 0
    144       1.7       sjg .else
    145      1.20    rillig .  error
    146       1.7       sjg .endif
    147       1.9    rillig 
    148      1.20    rillig # Starting with var.c 1.226 from from 2020-07-02, the following condition
    149      1.20    rillig # triggered a warning: "String comparison operator should be either == or !=".
    150      1.20    rillig #
    151      1.20    rillig # The left-hand side of the '&&' evaluated to false, which should have made
    152      1.20    rillig # the right-hand side irrelevant.
    153      1.20    rillig #
    154      1.20    rillig # On the right-hand side of the '&&', the expression ${INDIR_UNDEF} was
    155      1.20    rillig # defined and had the value '${UNDEF}', but the nested variable UNDEF was
    156      1.20    rillig # undefined.  The right hand side "${INDIR_UNDEF}" still needed to be parsed,
    157      1.20    rillig # and in parse-only mode, the "value" of the parsed expression was the
    158      1.20    rillig # uninterpreted variable value, in this case '${UNDEF}'.  And even though the
    159      1.20    rillig # right hand side of the '&&' should have been irrelevant, the two sides of
    160      1.20    rillig # the comparison were still parsed and evaluated.  Comparing these two values
    161      1.20    rillig # numerically was not possible since the string '${UNDEF}' is not a number,
    162      1.20    rillig # so the comparison fell back to string comparison, which then complained
    163      1.20    rillig # about the '>' operator.
    164      1.13    rillig #
    165      1.13    rillig # This was fixed in cond.c 1.79 from 2020-07-09 by not evaluating irrelevant
    166      1.13    rillig # comparisons.  Instead, they are only parsed and then discarded.
    167      1.13    rillig #
    168      1.13    rillig # At that time, there was not enough debug logging to see the details in the
    169      1.13    rillig # -dA log.  To actually see it, add debug logging at the beginning and end of
    170      1.13    rillig # Var_Parse.
    171      1.20    rillig .if defined(UNDEF) && ${INDIR_UNDEF} < ${NUMBER}
    172      1.20    rillig .  error
    173      1.20    rillig .endif
    174      1.20    rillig # Adding a ':U' modifier to the irrelevant expression didn't help, as that
    175      1.20    rillig # expression was only parsed, not evaluated.  The resulting literal string
    176      1.20    rillig # '${INDIR_UNDEF:U2}' was not numeric either, for the same reason as above.
    177      1.20    rillig .if defined(UNDEF) && ${INDIR_UNDEF:U2} < ${NUMBER}
    178      1.20    rillig .  error
    179      1.20    rillig .endif
    180      1.20    rillig 
    181      1.21    rillig 
    182      1.21    rillig # Since cond.c 1.76 from 2020.06.28 and before var.c 1.225 from 2020.07.01,
    183      1.21    rillig # the following snippet resulted in the error message 'Variable VAR is
    184      1.21    rillig # recursive'.  The condition '0' evaluated to false, which made the right-hand
    185      1.21    rillig # side of the '&&' irrelevant.  Back then, irrelevant condition parts were
    186      1.21    rillig # still evaluated, but in "irrelevant mode", which allowed undefined variables
    187      1.21    rillig # to occur in expressions.  In this mode, the variable name 'VAR' was
    188      1.21    rillig # unnecessarily evaluated, resulting in the expression '${VAR${:U1}}'.  In
    189      1.21    rillig # this expression, the variable name was 'VAR${:U1}', and of this variable
    190      1.21    rillig # name, only the fixed part 'VAR' was evaluated, without the part '${:U1}'.
    191      1.21    rillig # This partial evaluation led to the wrong error message about 'VAR' being
    192      1.21    rillig # recursive.
    193      1.21    rillig VAR=	${VAR${:U1}}
    194      1.21    rillig .if 0 && !empty(VAR)
    195      1.21    rillig .endif
    196      1.21    rillig 
    197      1.21    rillig 
    198      1.20    rillig # Enclosing the expression in double quotes changes how that expression is
    199      1.20    rillig # evaluated.  In irrelevant expressions that are enclosed in double quotes,
    200      1.20    rillig # expressions based on undefined variables are allowed and evaluate to an
    201      1.20    rillig # empty string.
    202      1.20    rillig #
    203      1.20    rillig # The manual page stated from at least 1993 on that irrelevant conditions were
    204      1.20    rillig # not evaluated, but that was wrong.  These conditions were evaluated, the
    205      1.20    rillig # only difference was that undefined variables in them didn't trigger an
    206      1.20    rillig # error.  Since numeric conditions are quite rare, this subtle difference
    207      1.20    rillig # didn't catch much attention, as most other conditions such as pattern
    208      1.20    rillig # matches or equality comparisons worked fine and never produced error
    209      1.20    rillig # messages.
    210      1.20    rillig .if defined(UNDEF) && "${INDIR_UNDEF}" < ${NUMBER}
    211      1.20    rillig .  error
    212       1.7       sjg .endif
    213       1.9    rillig 
    214      1.20    rillig # Since the condition is relevant, the indirect undefined variable is
    215      1.20    rillig # evaluated as usual, resolving nested undefined expressions to an empty
    216      1.20    rillig # string.
    217      1.20    rillig #
    218      1.20    rillig # Comparing an empty string numerically is not possible, however, make has an
    219      1.20    rillig # ugly hack in TryParseNumber that treats an empty string as a valid numerical
    220      1.20    rillig # value, thus hiding bugs in the makefile.
    221      1.20    rillig .if ${INDIR_UNDEF} < ${NUMBER}
    222      1.20    rillig #  only due to the ugly hack
    223       1.7       sjg .else
    224      1.20    rillig .  error
    225       1.7       sjg .endif
    226       1.9    rillig 
    227      1.20    rillig # Due to the quotes around the left-hand side of the '<', the operand is
    228      1.20    rillig # marked as a string, thus preventing a numerical comparison.
    229      1.13    rillig #
    230  1.23.2.1  perseant # expect+1: Comparison with "<" requires both operands "" and "42" to be numeric
    231      1.20    rillig .if "${INDIR_UNDEF}" < ${NUMBER}
    232      1.20    rillig .  info yes
    233       1.7       sjg .else
    234      1.20    rillig .  info no
    235       1.7       sjg .endif
    236       1.9    rillig 
    237      1.20    rillig # The right-hand side of '||' is irrelevant and thus not evaluated.
    238      1.20    rillig .if 1 || ${INDIR_NUMBER} < ${NUMBER}
    239       1.7       sjg .else
    240      1.20    rillig .  error
    241      1.20    rillig .endif
    242      1.20    rillig 
    243      1.20    rillig # The right-hand side of '||' is relevant and thus evaluated normally.
    244      1.20    rillig .if 0 || ${INDIR_NUMBER} < ${NUMBER}
    245      1.20    rillig .  error
    246       1.7       sjg .endif
    247       1.9    rillig 
    248      1.20    rillig # The right-hand side of '||' evaluates to an empty string, as the variable
    249      1.20    rillig # 'INDIR_UNDEF' is defined, therefore the modifier ':U2' has no effect.
    250      1.20    rillig # Comparing an empty string numerically is not possible, however, make has an
    251      1.20    rillig # ugly hack in TryParseNumber that treats an empty string as a valid numerical
    252      1.20    rillig # value, thus hiding bugs in the makefile.
    253      1.20    rillig .if 0 || ${INDIR_UNDEF:U2} < ${NUMBER}
    254      1.20    rillig #  only due to the ugly hack
    255       1.7       sjg .else
    256      1.20    rillig .  error
    257       1.7       sjg .endif
    258      1.20    rillig 
    259       1.7       sjg 
    260      1.15    rillig # The right-hand side of the '&&' is irrelevant since the left-hand side
    261      1.15    rillig # already evaluates to false.  Before cond.c 1.79 from 2020-07-09, it was
    262      1.15    rillig # expanded nevertheless, although with a small modification:  undefined
    263      1.15    rillig # variables may be used in these expressions without generating an error.
    264      1.12    rillig .if defined(UNDEF) && ${UNDEF} != "undefined"
    265      1.12    rillig .  error
    266      1.12    rillig .endif
    267      1.12    rillig 
    268      1.18    rillig 
    269      1.18    rillig # Ensure that irrelevant conditions do not influence the result of the whole
    270      1.18    rillig # condition.  As of cond.c 1.302 from 2021-12-11, an irrelevant function call
    271      1.20    rillig # evaluated to true (see CondParser_FuncCall and CondParser_FuncCallEmpty), an
    272      1.20    rillig # irrelevant comparison evaluated to false (see CondParser_Comparison).
    273      1.18    rillig #
    274      1.18    rillig # An irrelevant true bubbles up to the outermost CondParser_And, where it is
    275      1.18    rillig # ignored.  An irrelevant false bubbles up to the outermost CondParser_Or,
    276      1.18    rillig # where it is ignored.
    277      1.18    rillig #
    278      1.18    rillig # If the condition parser should ever be restructured, the bubbling up of the
    279      1.18    rillig # irrelevant evaluation results might show up accidentally.  Prevent this.
    280      1.18    rillig DEF=	defined
    281      1.18    rillig .undef UNDEF
    282      1.18    rillig 
    283      1.18    rillig .if 0 && defined(DEF)
    284      1.18    rillig .  error
    285      1.18    rillig .endif
    286      1.18    rillig 
    287      1.18    rillig .if 1 && defined(DEF)
    288      1.18    rillig .else
    289      1.18    rillig .  error
    290      1.18    rillig .endif
    291      1.18    rillig 
    292      1.18    rillig .if 0 && defined(UNDEF)
    293      1.18    rillig .  error
    294      1.18    rillig .endif
    295      1.18    rillig 
    296      1.18    rillig .if 1 && defined(UNDEF)
    297      1.18    rillig .  error
    298      1.18    rillig .endif
    299      1.18    rillig 
    300      1.18    rillig .if 0 || defined(DEF)
    301      1.18    rillig .else
    302      1.18    rillig .  error
    303      1.18    rillig .endif
    304      1.18    rillig 
    305      1.18    rillig .if 1 || defined(DEF)
    306      1.18    rillig .else
    307      1.18    rillig .  error
    308      1.18    rillig .endif
    309      1.18    rillig 
    310      1.18    rillig .if 0 || defined(UNDEF)
    311      1.18    rillig .  error
    312      1.18    rillig .endif
    313      1.18    rillig 
    314      1.18    rillig .if 1 || defined(UNDEF)
    315      1.18    rillig .else
    316      1.18    rillig .  error
    317      1.18    rillig .endif
    318      1.18    rillig 
    319      1.18    rillig 
    320       1.1    rillig all:
    321