freebsd-dev/contrib/bmake/unit-tests/var-scope-local.mk
Simon J. Gerraty 1d3f2ddc32 Merge bmake-20220330
Merge commit 'a052cb432096794be5070dc68a2b302eaf5a4783'
2022-04-03 12:58:43 -07:00

237 lines
10 KiB
Makefile

# $NetBSD: var-scope-local.mk,v 1.5 2022/02/09 21:09:24 rillig Exp $
#
# Tests for target-local variables, such as ${.TARGET} or $@. These variables
# are relatively short-lived as they are created just before making the
# target. In contrast, global variables are typically created when the
# makefiles are read in.
#
# The 7 built-in target-local variables are listed in the manual page. They
# are defined just before the target is actually made. Additional
# target-local variables can be defined in dependency lines like
# 'target: VAR=value', one at a time.
.MAIN: all
# The target-local variables can be used in expressions, just like other
# variables. When these expressions are evaluated outside of a target, these
# expressions are not yet expanded, instead their text is preserved, to allow
# these expressions to expand right in time when the target-local variables
# are actually set.
#
# Conditions from .if directives are evaluated in the scope of the command
# line, which means that variables from the command line, from the global
# scope and from the environment are resolved, in this order (but see the
# command line option '-e'). In that phase, expressions involving
# target-local variables need to be preserved, including the exact names of
# the variables.
#
# Each of the built-in target-local variables has two equivalent names, for
# example '@' is equivalent to '.TARGET'. The implementation might
# canonicalize these aliases at some point, and that might be surprising.
# This aliasing happens for single-character variable names like $@ or $<
# (see VarFind, CanonicalVarname), but not for braced or parenthesized
# expressions like ${@}, ${.TARGET} ${VAR:Mpattern} (see Var_Parse,
# ParseVarname).
#
# In the following condition, make expands '$@' to the long-format alias
# '$(.TARGET)'; note that the alias is not written with braces, as would be
# common in BSD makefiles, but with parentheses. This alternative spelling
# behaves the same though.
.if $@ != "\$\(.TARGET)"
. error
.endif
# In the long form of writing a target-local variable, the text of the
# expression is preserved exactly as written, no matter whether it is written
# with '{' or '('.
.if ${@} != "\$\{@}"
. error
.endif
.if $(@) != "\$\(@)"
. error
.endif
# If the variable expression contains modifiers, the behavior depends on the
# actual modifiers. The modifier ':M' keeps the expression in the state
# 'undefined'. Since the expression is still undefined after evaluating all
# the modifiers, the value of the expression is discarded and the expression
# text is used instead. This preserves the expressions based on target-local
# variables as long as possible.
.if ${@:M*} != "\$\{@:M*}"
. error
.endif
# In the following examples, the expressions are based on target-local
# variables but use the modifier ':L', which turns an undefined expression
# into a defined one. At the end of evaluating the expression, the state of
# the expression is not 'undefined' anymore. The value of the expression
# is the name of the variable, since that's what the modifier ':L' does.
.if ${@:L} != "@"
. error
.endif
.if ${.TARGET:L} != ".TARGET"
. error
.endif
.if ${@F:L} != "@F"
. error
.endif
.if ${@D:L} != "@D"
. error
.endif
# Additional target-local variables may be defined in dependency lines.
.MAKEFLAGS: -dv
# In the following line, the ':=' may either be interpreted as an assignment
# operator or as the dependency operator ':', followed by an empty variable
# name and the assignment operator '='. It is the latter since in an
# assignment, the left-hand side must be at most a single word. The empty
# variable name is expanded twice, once for 'one' and once for 'two'.
# expect: Var_SetExpand: variable name "" expands to empty string, with value "three" - ignored
# expect: Var_SetExpand: variable name "" expands to empty string, with value "three" - ignored
one two:=three
# If the two targets to the left are generated by a variable expression, the
# line is parsed as a variable assignment since its left-hand side is a single
# word.
# expect: Global: one two = three
${:Uone two}:=three
.MAKEFLAGS: -d0
.SUFFIXES: .c .o
# One of the dynamic target-local variables is '.TARGET'. Since this is not
# a suffix transformation rule, the variable '.IMPSRC' is not defined.
# expect: : Making var-scope-local.c out of nothing.
var-scope-local.c:
: Making ${.TARGET} ${.IMPSRC:Dfrom ${.IMPSRC}:Uout of nothing}.
# This is a suffix transformation rule, so both '.TARGET' and '.IMPSRC' are
# defined.
# expect: : Making var-scope-local.o from var-scope-local.c.
# expect: : Making basename "var-scope-local.o" in "." from "var-scope-local.c" in ".".
.c.o:
: Making ${.TARGET} from ${.IMPSRC}.
# The local variables @F, @D, <F, <D are legacy forms.
# See the manual page for details.
: Making basename "${@F}" in "${@D}" from "${<F}" in "${<D}".
# expect: : all overwritten
all: var-scope-local.o
# The ::= modifier overwrites the .TARGET variable in the node
# 'all', not in the global scope. This can be seen with the -dv
# option, looking for "all: @ = overwritten".
: ${.TARGET} ${.TARGET::=overwritten}${.TARGET}
# Begin tests for custom target-local variables, for all 5 variable assignment
# operators.
all: var-scope-local-assign.o
all: var-scope-local-append.o
all: var-scope-local-append-global.o
all: var-scope-local-default.o
all: var-scope-local-subst.o
all: var-scope-local-shell.o
var-scope-local-assign.o \
var-scope-local-append.o \
var-scope-local-append-global.o \
var-scope-local-default.o \
var-scope-local-subst.o \
var-scope-local-shell.o:
: Making ${.TARGET} with VAR="${VAR}".
# Target-local variables are enabled by default. Force them to be enabled
# just in case a test above has disabled them.
.MAKE.TARGET_LOCAL_VARIABLES= yes
VAR= global
# If the sources of a dependency line look like a variable assignment, make
# treats them as such. There is only a single variable assignment per
# dependency line, which makes whitespace around the assignment operator
# irrelevant.
#
# expect-reset
# expect: : Making var-scope-local-assign.o with VAR="local".
var-scope-local-assign.o: VAR= local
# Assignments using '+=' do *not* look up the global value, instead they only
# look up the variable in the target's own scope.
var-scope-local-append.o: VAR+= local
# Once a variable is defined in the target-local scope, appending using '+='
# behaves as expected. Note that the expression '${.TARGET}' is not resolved
# when parsing the dependency line, its evaluation is deferred until the
# target is actually made.
# expect: : Making var-scope-local-append.o with VAR="local to var-scope-local-append.o".
var-scope-local-append.o: VAR += to ${.TARGET}
# To access the value of a global variable, use a variable expression. This
# expression is expanded before parsing the whole dependency line. Since the
# expansion happens to the right of the dependency operator ':', the expanded
# text does not influence parsing of the dependency line. Since the expansion
# happens to the right of the assignment operator '=', the expanded text does
# not influence the parsing of the variable assignment. The effective
# variable assignment, after expanding the whole line first, is thus
# 'VAR= global+local'.
# expect: : Making var-scope-local-append-global.o with VAR="global+local".
var-scope-local-append-global.o: VAR= ${VAR}+local
var-scope-local-default.o: VAR ?= first
var-scope-local-default.o: VAR ?= second
# XXX: '?=' does look at the global variable. That's a long-standing
# inconsistency between the assignment operators '+=' and '?='. See
# Var_AppendExpand and VarAssign_Eval.
# expect: : Making var-scope-local-default.o with VAR="global".
# Using the variable assignment operator ':=' provides another way of
# accessing a global variable and extending it with local modifications. The
# '$' has to be written as '$$' though to survive the expansion of the
# dependency line as a whole. After that, the parser sees the variable
# assignment as 'VAR := ${VAR}+local' and searches for the variable 'VAR' in
# the usual scopes, picking up the variable from the global scope.
# expect: : Making var-scope-local-subst.o with VAR="global+local".
var-scope-local-subst.o: VAR := $${VAR}+local
# The variable assignment operator '!=' assigns the output of the shell
# command, as everywhere else. The shell command is run when the dependency
# line is parsed.
var-scope-local-shell.o: VAR != echo output
# While VAR=use will be set for a .USE node, it will never be seen since only
# the ultimate target's context is searched; the variable assignments from the
# .USE target are not copied to the ultimate target's.
# expect: : var-scope-local-use.o uses .USE VAR="global"
a_use: .USE VAR=use
: ${.TARGET} uses .USE VAR="${VAR}"
all: var-scope-local-use.o
var-scope-local-use.o: a_use
# Since parse.c 1.656 from 2022-01-27 and before parse.c 1.662 from
# 2022-02-05, there was an out-of-bounds read in Parse_IsVar when looking for
# a variable assignment in a dependency line with trailing whitespace. Lines
# without trailing whitespace were not affected. Global variable assignments
# were guaranteed to have no trailing whitespace and were thus not affected.
#
# Try to reproduce some variants that may lead to a crash, depending on the
# memory allocator. To get a crash, the terminating '\0' of the line must be
# the last byte of a memory page. The expression '${:U}' forces this trailing
# whitespace.
# On FreeBSD x86_64, a crash could in some cases be forced using the following
# line, which has length 47, so the terminating '\0' may end up at an address
# of the form 0xXXXX_XXXX_XXXX_Xfff:
Try_to_crash_FreeBSD.xxxxxxxxxxxxxxxxxx: 12345 ${:U}
# The following line has length 4095, so line[4095] == '\0'. If the line is
# allocated on a page boundary and the following page is not mapped, this line
# leads to a segmentation fault.
${:U:range=511:@_@1234567@:ts.}: 12345 ${:U}
# The following line has length 8191, so line[8191] == '\0'. If the line is
# allocated on a page boundary and the following page is not mapped, this line
# leads to a segmentation fault.
${:U:range=1023:@_@1234567@:ts.}: 12345 ${:U}
12345: