`configure_file`: update \@ escape logic

When configuring a 'meson' or 'cmake@' style file,
add a case for escaped variables using matched pairs of
`\@` i.e. `\@foo\@ -> @foo@`.

The match for @var@ has been amended with a negative lookbehind
to ensure that any occurrances of `\@foo@` are not evaluated to
`\bar`.

The previous behaviour, matching `\@` and escaping only that character,
had undesirable side effects including mangling valid perl when
configuring files.

Closes: https://github.com/mesonbuild/meson/issues/7165
pull/11445/head
Matt Jolly 5 months ago committed by Jussi Pakkanen
parent 8967090149
commit 410bdf8c6c
  1. 39
      mesonbuild/utils/universal.py
  2. 37
      test cases/common/14 configure file/config6.h.in
  3. 2
      test cases/common/14 configure file/meson.build
  4. 12
      test cases/common/14 configure file/prog6.c

@ -1182,24 +1182,21 @@ def do_replacement(regex: T.Pattern[str], line: str,
variable_format: Literal['meson', 'cmake', 'cmake@'],
confdata: T.Union[T.Dict[str, T.Tuple[str, T.Optional[str]]], 'ConfigurationData']) -> T.Tuple[str, T.Set[str]]:
missing_variables: T.Set[str] = set()
if variable_format == 'cmake':
start_tag = '${'
backslash_tag = '\\${'
else:
start_tag = '@'
backslash_tag = '\\@'
def variable_replace(match: T.Match[str]) -> str:
# Pairs of escape characters before '@' or '\@'
# Pairs of escape characters before '@', '\@', '${' or '\${'
if match.group(0).endswith('\\'):
num_escapes = match.end(0) - match.start(0)
return '\\' * (num_escapes // 2)
# Single escape character and '@'
elif match.group(0) == backslash_tag:
return start_tag
# Template variable to be replaced
# Handle cmake escaped \${} tags
elif variable_format == 'cmake' and match.group(0) == '\\${':
return '${'
# \@escaped\@ variables
elif match.groupdict().get('escaped') is not None:
return match.group('escaped')[1:-2]+'@'
else:
varname = match.group(1)
# Template variable to be replaced
varname = match.group('variable')
var_str = ''
if varname in confdata:
var, _ = confdata.get(varname)
@ -1280,11 +1277,23 @@ def do_define(regex: T.Pattern[str], line: str, confdata: 'ConfigurationData',
def get_variable_regex(variable_format: Literal['meson', 'cmake', 'cmake@'] = 'meson') -> T.Pattern[str]:
# Only allow (a-z, A-Z, 0-9, _, -) as valid characters for a define
# Also allow escaping '@' with '\@'
if variable_format in {'meson', 'cmake@'}:
regex = re.compile(r'(?:\\\\)+(?=\\?@)|\\@|@([-a-zA-Z0-9_]+)@')
# Also allow escaping pairs of '@' with '\@'
regex = re.compile(r'''
(?:\\\\)+(?=\\?@) # Matches multiple backslashes followed by an @ symbol
| # OR
(?<!\\)@(?P<variable>[-a-zA-Z0-9_]+)@ # Match a variable enclosed in @ symbols and capture the variable name; no matches beginning with '\@'
| # OR
(?P<escaped>\\@[-a-zA-Z0-9_]+\\@) # Match an escaped variable enclosed in @ symbols
''', re.VERBOSE)
else:
regex = re.compile(r'(?:\\\\)+(?=\\?\$)|\\\${|\${([-a-zA-Z0-9_]+)}')
regex = re.compile(r'''
(?:\\\\)+(?=\\?\$) # Match multiple backslashes followed by a dollar sign
| # OR
\\\${ # Match a backslash followed by a dollar sign and an opening curly brace
| # OR
\${(?P<variable>[-a-zA-Z0-9_]+)} # Match a variable enclosed in curly braces and capture the variable name
''', re.VERBOSE)
return regex
def do_conf_str(src: str, data: T.List[str], confdata: 'ConfigurationData',

@ -1,19 +1,40 @@
/* No escape */
#define MESSAGE1 "@var1@"
/* Single escape means no replace */
#define MESSAGE2 "\@var1@"
/* Escaped whole variable */
#define MESSAGE2 "\\@var1\\@"
/* Replace pairs of escapes before '@' or '\@' with escape characters
* (note we have to double number of pairs due to C string escaping)
*/
#define MESSAGE3 "\\\\@var1@"
/* Pairs of escapes and then single escape to avoid replace */
#define MESSAGE4 "\\\\\@var1@"
/* Pairs of escapes and then an escaped variable */
#define MESSAGE4 "\\\\\@var1\@"
/* Check escaped variable does not overlap following variable */
#define MESSAGE5 "\@var1@var2@"
/* We don't gobble \@ prefixing some text */
#define MESSAGE5 "\\\\@var1"
/* Check escape character outside variables */
#define MESSAGE6 "\\ @ \@ \\\\@ \\\\\@"
/* Check escape character outside variables
\ @ \@ */
#define MESSAGE6 "\\ @ \\\\@"
/* Catch any edge cases */
/* no substitution - not a variable */
#define MESSAGE7 "@var1"
/* Escaped variable followed by another variable */
#define MESSAGE8 "\\\\@var1@var2@"
/* Variable followed by another variable */
#define MESSAGE9 "@var1@var2@"
/* Variable followed by another variable and escaped */
#define MESSAGE10 "@var1@var2\\\\@"
/* Lots of substitutions in a row*/
#define MESSAGE11 "@var1@@var2@@var3@@var4@"
/* This should never happen in the real world, right? */
#define MESSAGE12 "@var1@var2\\\\@var3@var4\\\\@"

@ -143,6 +143,8 @@ test('test5', executable('prog5', 'prog5.c'))
conf6 = configuration_data()
conf6.set('var1', 'foo')
conf6.set('var2', 'bar')
conf6.set('var3', 'baz')
conf6.set('var4', 'qux')
configure_file(
input : 'config6.h.in',
output : '@BASENAME@',

@ -4,8 +4,14 @@
int main(void) {
return strcmp(MESSAGE1, "foo")
|| strcmp(MESSAGE2, "@var1@")
|| strcmp(MESSAGE3, "\\foo")
|| strcmp(MESSAGE3, "\\@var1@")
|| strcmp(MESSAGE4, "\\@var1@")
|| strcmp(MESSAGE5, "@var1bar")
|| strcmp(MESSAGE6, "\\ @ @ \\@ \\@");
|| strcmp(MESSAGE5, "\\@var1")
|| strcmp(MESSAGE6, "\\ @ \\@")
|| strcmp(MESSAGE7, "@var1")
|| strcmp(MESSAGE8, "\\@var1bar")
|| strcmp(MESSAGE9, "foovar2@")
|| strcmp(MESSAGE10, "foovar2\\@")
|| strcmp(MESSAGE11, "foobarbazqux")
|| strcmp(MESSAGE12, "foovar2\\@var3@var4\\@");
}

Loading…
Cancel
Save