Repository: mesonbuild/meson

Issue:
RecursionError when running `meson configure` in source directory (on today's meson master; commit d31db565e1405b303390)
Running `meson configure` in [Mesa's source directory](https://gitlab.freedesktop.org/mesa/mesa) (at Mesa commit 8b3baa274406e9bcde42 in case the issue disappears later) crashes:

```
Traceback (most recent call last):
  File "/home/eric/dev/scm/meson/mesonbuild/mesonmain.py", line 127, in run
    return options.run_func(options)
  File "/home/eric/dev/scm/meson/mesonbuild/mconf.py", line 200, in run
    c = Conf(builddir)
  File "/home/eric/dev/scm/meson/mesonbuild/mconf.py", line 56, in __init__
    intr.analyze()
  File "/home/eric/dev/scm/meson/mesonbuild/ast/introspection.py", line 276, in analyze
    self.run()
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 407, in run
    self.evaluate_codeblock(self.ast, start=1)
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 431, in evaluate_codeblock
    raise e
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 425, in evaluate_codeblock
    self.evaluate_statement(cur)
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 436, in evaluate_statement
    return self.function_call(cur)
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 773, in function_call
    return func(node, posargs, kwargs)
  File "/home/eric/dev/scm/meson/mesonbuild/ast/interpreter.py", line 162, in func_subdir
    self.evaluate_codeblock(codeblock)
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 431, in evaluate_codeblock
    raise e
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 425, in evaluate_codeblock
    self.evaluate_statement(cur)
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 436, in evaluate_statement
    return self.function_call(cur)
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 773, in function_call
    return func(node, posargs, kwargs)
  File "/home/eric/dev/scm/meson/mesonbuild/ast/interpreter.py", line 162, in func_subdir
    self.evaluate_codeblock(codeblock)
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 431, in evaluate_codeblock
    raise e
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 425, in evaluate_codeblock
    self.evaluate_statement(cur)
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 446, in evaluate_statement
    return self.evaluate_if(cur)
  File "/home/eric/dev/scm/meson/mesonbuild/ast/interpreter.py", line 232, in evaluate_if
    self.evaluate_codeblock(i.block)
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 431, in evaluate_codeblock
    raise e
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 425, in evaluate_codeblock
    self.evaluate_statement(cur)
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 436, in evaluate_statement
    return self.function_call(cur)
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 773, in function_call
    return func(node, posargs, kwargs)
  File "/home/eric/dev/scm/meson/mesonbuild/ast/interpreter.py", line 162, in func_subdir
    self.evaluate_codeblock(codeblock)
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 431, in evaluate_codeblock
    raise e
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 425, in evaluate_codeblock
    self.evaluate_statement(cur)
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 438, in evaluate_statement
    return self.assignment(cur)
  File "/home/eric/dev/scm/meson/mesonbuild/ast/interpreter.py", line 244, in assignment
    self.assign_vals[node.var_name] = [self.evaluate_statement(node.value)] # Evaluate the value just in case
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 436, in evaluate_statement
    return self.function_call(cur)
  File "/home/eric/dev/scm/meson/mesonbuild/interpreterbase.py", line 764, in function_call
    (posargs, kwargs) = self.reduce_arguments(node.args)
  File "/home/eric/dev/scm/meson/mesonbuild/ast/interpreter.py", line 203, in reduce_arguments
    return self.flatten_args(args.arguments), args.kwargs
  File "/home/eric/dev/scm/meson/mesonbuild/ast/interpreter.py", line 305, in flatten_args
    flattend_args += self.flatten_args(quick_resolve(i), include_unknown_args, id_loop_detect + [i.value])
  File "/home/eric/dev/scm/meson/mesonbuild/ast/interpreter.py", line 277, in flatten_args
    args = self.flatten_args(l, include_unknown_args, id_loop_detect) + self.flatten_args(r, include_unknown_args, id_loop_detect)
  File "/home/eric/dev/scm/meson/mesonbuild/ast/interpreter.py", line 277, in flatten_args
    args = self.flatten_args(l, include_unknown_args, id_loop_detect) + self.flatten_args(r, include_unknown_args, id_loop_detect)
  File "/home/eric/dev/scm/meson/mesonbuild/ast/interpreter.py", line 277, in flatten_args
    args = self.flatten_args(l, include_unknown_args, id_loop_detect) + self.flatten_args(r, include_unknown_args, id_loop_detect)
  [Previous line repeated 962 more times]
  File "/home/eric/dev/scm/meson/mesonbuild/ast/interpreter.py", line 247, in flatten_args
    def quick_resolve(n: BaseNode, loop_detect: Optional[List[str]] = None) -> Any:
  File "/usr/lib/python3.7/typing.py", line 248, in inner
    return cached(*args, **kwds)
  File "/usr/lib/python3.7/typing.py", line 664, in __hash__
    return hash((self.__origin__, self.__args__))
RecursionError: maximum recursion depth exceeded while calling a Python object
```


Discussion:
This is a bug in the ast interpreter. I will try to fix this. Also, #5375 might be related to #5286 but I will have to look into this.

Resolving patch:
diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py
--- a/mesonbuild/ast/interpreter.py
+++ b/mesonbuild/ast/interpreter.py
@@ -260,6 +260,12 @@ def quick_resolve(n: BaseNode, loop_detect: Optional[List[str]] = None) -> Any:
             id_loop_detect = []
         flattend_args = []
 
+        if isinstance(args, BaseNode):
+            assert(hasattr(args, 'ast_id'))
+            if args.ast_id in id_loop_detect:
+                return [] # Loop detected
+            id_loop_detect += [args.ast_id]
+
         if isinstance(args, ArrayNode):
             args = [x for x in args.args.arguments]
 
@@ -301,8 +307,8 @@ def quick_resolve(n: BaseNode, loop_detect: Optional[List[str]] = None) -> Any:
 
         # Resolve the contents of args
         for i in args:
-            if isinstance(i, IdNode) and i.value not in id_loop_detect:
-                flattend_args += self.flatten_args(quick_resolve(i), include_unknown_args, id_loop_detect + [i.value])
+            if isinstance(i, IdNode):
+                flattend_args += self.flatten_args(quick_resolve(i), include_unknown_args, id_loop_detect)
             elif isinstance(i, (ArrayNode, ArgumentNode, ArithmeticNode, MethodNode)):
                 flattend_args += self.flatten_args(i, include_unknown_args, id_loop_detect)
             elif isinstance(i, mparser.ElementaryNode):
diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py
--- a/mesonbuild/ast/introspection.py
+++ b/mesonbuild/ast/introspection.py
@@ -118,7 +118,7 @@ def do_subproject(self, dirname):
         subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir)
         subpr = os.path.join(subproject_dir_abs, dirname)
         try:
-            subi = IntrospectionInterpreter(subpr, '', self.backend, cross_file=self.cross_file, subproject=dirname, subproject_dir=self.subproject_dir, env=self.environment)
+            subi = IntrospectionInterpreter(subpr, '', self.backend, cross_file=self.cross_file, subproject=dirname, subproject_dir=self.subproject_dir, env=self.environment, visitors=self.visitors)
             subi.analyze()
             subi.project_data['name'] = dirname
             self.project_data['subprojects'] += [subi.project_data]
diff --git a/mesonbuild/mconf.py b/mesonbuild/mconf.py
--- a/mesonbuild/mconf.py
+++ b/mesonbuild/mconf.py
@@ -14,6 +14,7 @@
 
 import os
 from . import coredata, environment, mesonlib, build, mintro, mlog
+from .ast import AstIDGenerator
 
 def add_arguments(parser):
     coredata.register_builtin_arguments(parser)
@@ -52,7 +53,7 @@ def __init__(self, build_dir):
             # Make sure that log entries in other parts of meson don't interfere with the JSON output
             mlog.disable()
             self.source_dir = os.path.abspath(os.path.realpath(self.build_dir))
-            intr = mintro.IntrospectionInterpreter(self.source_dir, '', 'ninja')
+            intr = mintro.IntrospectionInterpreter(self.source_dir, '', 'ninja', visitors = [AstIDGenerator()])
             intr.analyze()
             # Reenable logging just in case
             mlog.enable()


Test patch:
diff --git a/test cases/unit/55 introspection/meson.build b/test cases/unit/55 introspection/meson.build
--- a/test cases/unit/55 introspection/meson.build	
+++ b/test cases/unit/55 introspection/meson.build	
@@ -34,6 +34,13 @@ systype = '@0@, @1@, @2@'.format(systype, host_machine.cpu_family(), host_machin
 message(systype)
 ### END: Test inspired by taisei
 
+# Minimal code version to produce bug #5376
+# Code inspired by https://github.com/mesa3d/mesa/blob/974c4d679c23373dbed386c696e3e3bc1bfa23ae/meson.build#L1341-L1347
+osmesa_lib_name = 'OSMesa'
+osmesa_bits = '8'
+osmesa_lib_name = osmesa_lib_name + osmesa_bits
+message(osmesa_lib_name) # Infinite recursion gets triggered here when the parameter osmesa_lib_name is resolved
+
 test('test case 1', t1)
 test('test case 2', t2)
 benchmark('benchmark 1', t3)