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