hdl.ir: reject elaboratables that elaborate to themselves.
authorwhitequark <whitequark@whitequark.org>
Sat, 11 Dec 2021 12:39:34 +0000 (12:39 +0000)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Fri, 31 Dec 2021 20:18:59 +0000 (20:18 +0000)
Fixes #592.

nmigen/hdl/ir.py
tests/test_hdl_ir.py

index 3c1ec6045d945fc6461d90eda08e5f48dfbb976e..45857b864b7001584cb108bd39cf0ca7a29c5db7 100644 (file)
@@ -34,7 +34,7 @@ class Fragment:
             elif isinstance(obj, Elaboratable):
                 code = obj.elaborate.__code__
                 obj._MustUse__used = True
-                obj = obj.elaborate(platform)
+                new_obj = obj.elaborate(platform)
             elif hasattr(obj, "elaborate"):
                 warnings.warn(
                     message="Class {!r} is an elaboratable that does not explicitly inherit from "
@@ -43,15 +43,18 @@ class Fragment:
                     category=RuntimeWarning,
                     stacklevel=2)
                 code = obj.elaborate.__code__
-                obj = obj.elaborate(platform)
+                new_obj = obj.elaborate(platform)
             else:
                 raise AttributeError("Object {!r} cannot be elaborated".format(obj))
-            if obj is None and code is not None:
+            if new_obj is obj:
+                raise RecursionError("Object {!r} elaborates to itself".format(obj))
+            if new_obj is None and code is not None:
                 warnings.warn_explicit(
                     message=".elaborate() returned None; missing return statement?",
                     category=UserWarning,
                     filename=code.co_filename,
                     lineno=code.co_firstlineno)
+            obj = new_obj
 
     def __init__(self):
         self.ports = SignalDict()
index c578bbe0d8af00023674445914310b055b5b6c0f..ce2c255722cc47069c54ec7e68b16e262a161560 100644 (file)
@@ -10,13 +10,18 @@ from nmigen.hdl.mem import *
 from .utils import *
 
 
-class BadElaboratable(Elaboratable):
+class ElaboratesToNone(Elaboratable):
     def elaborate(self, platform):
         return
 
 
+class ElaboratesToSelf(Elaboratable):
+    def elaborate(self, platform):
+        return self
+
+
 class FragmentGetTestCase(FHDLTestCase):
-    def test_get_wrong(self):
+    def test_get_wrong_none(self):
         with self.assertRaisesRegex(AttributeError,
                 r"^Object None cannot be elaborated$"):
             Fragment.get(None, platform=None)
@@ -25,7 +30,12 @@ class FragmentGetTestCase(FHDLTestCase):
                 r"^\.elaborate\(\) returned None; missing return statement\?$"):
             with self.assertRaisesRegex(AttributeError,
                     r"^Object None cannot be elaborated$"):
-                Fragment.get(BadElaboratable(), platform=None)
+                Fragment.get(ElaboratesToNone(), platform=None)
+
+    def test_get_wrong_self(self):
+        with self.assertRaisesRegex(RecursionError,
+                r"^Object <.+?ElaboratesToSelf.+?> elaborates to itself$"):
+            Fragment.get(ElaboratesToSelf(), platform=None)
 
 
 class FragmentGeneratedTestCase(FHDLTestCase):