Repository: pyodide/pyodide

Issue:
Low level crash due to destroyed proxy
The following code causes a low level failure:
```js
globalThis.f = function(x){ return x };
pyodide.runPython(`
   from pyodide import create_proxy
   from js import f
   p = create_proxy([])
   p.destroy()
   f(p) 
`);
```
This is a regression caused by #1573. Discovered while writing #1840.


Discussion:

Resolving patch:
diff --git a/conftest.py b/conftest.py
--- a/conftest.py
+++ b/conftest.py
@@ -226,6 +226,13 @@ def run_js_inner(self, code, check_code):
     def get_num_hiwire_keys(self):
         return self.run_js("return pyodide._module.hiwire.num_keys();")
 
+    @property
+    def force_test_fail(self) -> bool:
+        return self.run_js("return !!pyodide._module.fail_test;")
+
+    def clear_force_test_fail(self):
+        self.run_js("pyodide._module.fail_test = false;")
+
     def save_state(self):
         self.run_js("self.__savedState = pyodide._module.saveState();")
 
@@ -415,14 +422,20 @@ def pytest_runtest_call(item):
             trace_pyproxies
             and pytest.mark.skip_refcount_check.mark not in item.own_markers
         )
-        yield from test_wrapper_check_for_memory_leaks(
+        yield from extra_checks_test_wrapper(
             selenium, trace_hiwire_refs, trace_pyproxies
         )
     else:
         yield
 
 
-def test_wrapper_check_for_memory_leaks(selenium, trace_hiwire_refs, trace_pyproxies):
+def extra_checks_test_wrapper(selenium, trace_hiwire_refs, trace_pyproxies):
+    """Extra conditions for test to pass:
+    1. No explicit request for test to fail
+    2. No leaked JsRefs
+    3. No leaked PyProxys
+    """
+    selenium.clear_force_test_fail()
     init_num_keys = selenium.get_num_hiwire_keys()
     if trace_pyproxies:
         selenium.enable_pyproxy_tracing()
@@ -439,6 +452,8 @@ def test_wrapper_check_for_memory_leaks(selenium, trace_hiwire_refs, trace_pypro
         # get_result (we don't want to override the error message by raising a
         # different error here.)
         a.get_result()
+    if selenium.force_test_fail:
+        raise Exception("Test failure explicitly requested but no error was raised.")
     if trace_pyproxies and trace_hiwire_refs:
         delta_proxies = selenium.get_num_proxies() - init_num_proxies
         delta_keys = selenium.get_num_hiwire_keys() - init_num_keys


Test patch:
diff --git a/src/tests/test_pyodide.py b/src/tests/test_pyodide.py
--- a/src/tests/test_pyodide.py
+++ b/src/tests/test_pyodide.py
@@ -487,6 +487,24 @@ def __del__(self):
     )
 
 
+def test_return_destroyed_value(selenium):
+    selenium.run_js(
+        """
+        self.f = function(x){ return x };
+        pyodide.runPython(`
+            from pyodide import create_proxy, JsException
+            from js import f
+            p = create_proxy([])
+            p.destroy()
+            try:
+                f(p)
+            except JsException as e:
+                assert str(e) == "Error: Object has already been destroyed"
+        `);
+        """
+    )
+
+
 def test_docstrings_a():
     from _pyodide.docstring import get_cmeth_docstring, dedent_docstring
     from pyodide import JsProxy