Skip to content

Commit 4a57a5f

Browse files
Volker-Weissmannjpakkane
authored andcommitted
AstInterpreter: Fix dead-code-crash
Without this commit, the rewriter and the static introspection tool crash if `meson.build` contains something like ```meson if false foo = not_defined endif ``` or ```meson if false message(not_defined) endif ``` While it could be argued, that you should not write stuff like this, this used to raise a `MesonBugException`, which we have to fix. Fixes mesonbuild#14667
1 parent a3f43cb commit 4a57a5f

File tree

8 files changed

+49
-10
lines changed

8 files changed

+49
-10
lines changed

mesonbuild/ast/interpreter.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import itertools
1414
from pathlib import Path
1515

16-
from .. import mparser, mesonlib
16+
from .. import mparser, mesonlib, mlog
1717
from .. import environment
1818

1919
from ..interpreterbase import (
@@ -26,6 +26,7 @@
2626
default_resolve_key,
2727
is_disabled,
2828
UnknownValue,
29+
UndefinedVariable,
2930
InterpreterObject,
3031
)
3132

@@ -460,14 +461,14 @@ def evaluate_if(self, node: IfClauseNode) -> None:
460461
self.nesting.pop()
461462
for var_name in self.cur_assignments:
462463
potential_values = []
463-
oldval = self.get_cur_value(var_name, allow_none=True)
464-
if oldval is not None:
464+
oldval = self.get_cur_value_if_defined(var_name)
465+
if not isinstance(oldval, UndefinedVariable):
465466
potential_values.append(oldval)
466467
for nesting, value in self.cur_assignments[var_name]:
467468
if len(nesting) > len(self.nesting):
468469
potential_values.append(value)
469470
self.cur_assignments[var_name] = [(nesting, v) for (nesting, v) in self.cur_assignments[var_name] if len(nesting) <= len(self.nesting)]
470-
if len(potential_values) > 1 or (len(potential_values) > 0 and oldval is None):
471+
if len(potential_values) > 1 or (len(potential_values) > 0 and isinstance(oldval, UndefinedVariable)):
471472
uv = UnknownValue()
472473
for pv in potential_values:
473474
self.dataflow_dag.add_edge(pv, uv)
@@ -484,19 +485,28 @@ def func_files(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str,
484485
raise TypeError
485486
return ret
486487

487-
def get_cur_value(self, var_name: str, allow_none: bool = False) -> T.Union[BaseNode, UnknownValue, None]:
488+
def get_cur_value_if_defined(self, var_name: str) -> T.Union[BaseNode, UnknownValue, UndefinedVariable]:
488489
if var_name in {'meson', 'host_machine', 'build_machine', 'target_machine'}:
489490
return UnknownValue()
490-
ret = None
491+
ret: T.Union[BaseNode, UnknownValue, UndefinedVariable] = UndefinedVariable()
491492
for nesting, value in reversed(self.cur_assignments[var_name]):
492493
if len(self.nesting) >= len(nesting) and self.nesting[:len(nesting)] == nesting:
493494
ret = value
494495
break
495-
if ret is None and allow_none:
496-
return ret
497-
if ret is None and self.tainted:
496+
if isinstance(ret, UndefinedVariable) and self.tainted:
497+
return UnknownValue()
498+
return ret
499+
500+
def get_cur_value(self, var_name: str) -> T.Union[BaseNode, UnknownValue]:
501+
ret = self.get_cur_value_if_defined(var_name)
502+
if isinstance(ret, UndefinedVariable):
503+
path = mlog.get_relative_path(Path(self.current_node.filename), Path(os.getcwd()))
504+
mlog.warning(f"{path}:{self.current_node.lineno}:{self.current_node.colno} will always crash if executed, since a variable named `{var_name}` is not defined")
505+
# We could add more advanced analysis of code referencing undefined
506+
# variables, but it is probably not worth the effort and the
507+
# complexity. So we do the simplest thing, returning an
508+
# UnknownValue.
498509
return UnknownValue()
499-
assert ret is not None
500510
return ret
501511

502512
# The function `node_to_runtime_value` takes a node of the ast as an

mesonbuild/interpreterbase/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
'HoldableTypes',
6262

6363
'UnknownValue',
64+
'UndefinedVariable',
6465
]
6566

6667
from .baseobjects import (
@@ -85,6 +86,7 @@
8586
HoldableTypes,
8687

8788
UnknownValue,
89+
UndefinedVariable,
8890
)
8991

9092
from .decorators import (

mesonbuild/interpreterbase/baseobjects.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ class UnknownValue(MesonInterpreterObject):
127127
limitations in our code or because the value differs from machine to
128128
machine.'''
129129

130+
class UndefinedVariable(MesonInterpreterObject):
131+
'''This class is only used for the rewriter/static introspection tool and
132+
represents the `value` a meson-variable has if it was never written to.'''
133+
130134
HoldableTypes = (HoldableObject, int, bool, str, list, dict)
131135
TYPE_HoldableTypes = T.Union[TYPE_var, HoldableObject]
132136
InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=TYPE_HoldableTypes)

test cases/rewrite/7 tricky dataflow/addSrc.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,10 @@
6868
"type": "target",
6969
"target": "tgt6",
7070
"operation": "info"
71+
},
72+
{
73+
"type": "target",
74+
"target": "tgt7",
75+
"operation": "info"
7176
}
7277
]

test cases/rewrite/7 tricky dataflow/info.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,10 @@
2828
"type": "target",
2929
"target": "tgt6",
3030
"operation": "info"
31+
},
32+
{
33+
"type": "target",
34+
"target": "tgt7",
35+
"operation": "info"
3136
}
3237
]

test cases/rewrite/7 tricky dataflow/meson.build

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,9 @@ dont_add_here_6 = ['foo.c']
3333
gen = generator(find_program('cp'), output: '@BASENAME@_copy.c', arguments: ['@INPUT@', '@OUTPUT@'])
3434
generated = gen.process(dont_add_here_6)
3535
executable('tgt6', generated)
36+
37+
if false
38+
# Should produce a warning, but should not crash
39+
var = not_defined_1
40+
executable('tgt7', not_defined_2, var)
41+
endif

test cases/unit/120 rewrite/meson.build

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,5 +190,11 @@ if a \
190190
debug('help!')
191191
endif
192192

193+
if false
194+
# Should produce a warning, but should not crash
195+
foo = not_defined
196+
message(not_defined)
197+
endif
198+
193199

194200
# End of file comment with no linebreak

unittests/rewritetests.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,7 @@ def test_tricky_dataflow(self):
439439
'tgt4@exe': {'name': 'tgt4', 'sources': ['unknown'], 'extra_files': []},
440440
'tgt5@exe': {'name': 'tgt5', 'sources': ['unknown', 'new.c'], 'extra_files': []},
441441
'tgt6@exe': {'name': 'tgt6', 'sources': ['unknown', 'new.c'], 'extra_files': []},
442+
'tgt7@exe': {'name': 'tgt7', 'sources': ['unknown', 'unknown'], 'extra_files': []},
442443
}
443444
}
444445
self.assertEqualIgnoreOrder(out, expected)

0 commit comments

Comments
 (0)