1 1.5 rillig # $NetBSD: varparse-undef-partial.mk,v 1.5 2024/01/07 11:39:04 rillig Exp $ 2 1.2 rillig 3 1.2 rillig # When an undefined variable is expanded in a ':=' assignment, only the 4 1.4 rillig # initial '$' of the expression is skipped by the parser, while 5 1.2 rillig # the remaining expression is evaluated. In edge cases this can lead to 6 1.2 rillig # a completely different interpretation of the partially expanded text. 7 1.2 rillig 8 1.2 rillig LIST= ${DEF} ${UNDEF} ${VAR.${PARAM}} end 9 1.2 rillig DEF= defined 10 1.2 rillig PARAM= :Q 11 1.2 rillig 12 1.3 rillig # The expression ${VAR.${PARAM}} refers to the variable named "VAR.:Q", 13 1.2 rillig # with the ":Q" being part of the name. This variable is not defined, 14 1.5 rillig # therefore the initial '$' of that whole expression is skipped by the parser 15 1.5 rillig # (see VarSubstExpr) and the rest of the expression is expanded as usual. 16 1.2 rillig # 17 1.4 rillig # The resulting expression is ${VAR.:Q}, which means that the 18 1.2 rillig # interpretation of the ":Q" has changed from being part of the variable 19 1.2 rillig # name to being a variable modifier. This is a classical code injection. 20 1.2 rillig EVAL:= ${LIST} 21 1.2 rillig .if ${EVAL} != "defined end" 22 1.2 rillig . error ${EVAL} 23 1.2 rillig .endif 24 1.2 rillig 25 1.2 rillig # Define the possible outcomes, to see which of them gets expanded. 26 1.2 rillig VAR.= var-dot without parameter 27 1.2 rillig ${:UVAR.\:Q}= var-dot with parameter :Q 28 1.2 rillig 29 1.2 rillig # At this point, the variable "VAR." is defined, therefore the expression 30 1.3 rillig # ${VAR.:Q} is expanded, consisting of the variable name "VAR." and the 31 1.3 rillig # modifier ":Q". 32 1.2 rillig .if ${EVAL} != "defined var-dot\\ without\\ parameter end" 33 1.2 rillig . error ${EVAL} 34 1.2 rillig .endif 35 1.2 rillig 36 1.2 rillig # In contrast to the previous line, evaluating the original LIST again now 37 1.3 rillig # produces a different result since the variable named "VAR.:Q" is now 38 1.3 rillig # defined. It is expanded as usual, interpreting the ":Q" as part of the 39 1.4 rillig # variable name, as would be expected from reading the expression. 40 1.2 rillig EVAL:= ${LIST} 41 1.2 rillig .if ${EVAL} != "defined var-dot with parameter :Q end" 42 1.2 rillig . error ${EVAL} 43 1.2 rillig .endif 44 1.2 rillig 45 1.2 rillig # It's difficult to decide what the best behavior is in this situation. 46 1.2 rillig # Should the whole expression be skipped for now, or should the inner 47 1.2 rillig # subexpressions be expanded already? 48 1.2 rillig # 49 1.2 rillig # Example 1: 50 1.2 rillig # CFLAGS:= ${CFLAGS:N-W*} ${COPTS.${COMPILER}} 51 1.2 rillig # 52 1.2 rillig # The variable COMPILER typically contains an identifier and the variable is 53 1.2 rillig # not modified later. In this practical case, it does not matter whether the 54 1.2 rillig # expression is expanded early, or whether the whole ${COPTS.${COMPILER}} is 55 1.2 rillig # expanded as soon as the variable COPTS.${COMPILER} becomes defined. The 56 1.2 rillig # expression ${COMPILER} would be expanded several times, but in this simple 57 1.2 rillig # scenario there would not be any side effects. 58 1.2 rillig # 59 1.2 rillig # TODO: Add a practical example where early/lazy expansion actually makes a 60 1.2 rillig # difference. 61 1.2 rillig 62 1.2 rillig all: 63 1.2 rillig @: 64