From 639c1a1eca49e16ff3a0a30d7ef7026eced4d2e7 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Wed, 11 Sep 2024 15:03:31 -0400 Subject: [PATCH 1/5] Class method must declare self arg by python rules. Enforce it --- src/jsTypeFactory.cc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/jsTypeFactory.cc b/src/jsTypeFactory.cc index 60834188..24eb5c4e 100644 --- a/src/jsTypeFactory.cc +++ b/src/jsTypeFactory.cc @@ -379,14 +379,22 @@ bool callPyFunc(JSContext *cx, unsigned int argc, JS::Value *vp) { else { nNormalArgs = 1; PyObject *f = pyFunc; + bool isMethod; if (PyMethod_Check(pyFunc)) { f = PyMethod_Function(pyFunc); // borrowed reference nNormalArgs -= 1; // don't include the implicit `self` of the method as an argument + isMethod = true; + } else { + isMethod = false; } PyCodeObject *bytecode = (PyCodeObject *)PyFunction_GetCode(f); // borrowed reference PyObject *defaults = PyFunction_GetDefaults(f); // borrowed reference nDefaultArgs = defaults ? PyTuple_Size(defaults) : 0; - nNormalArgs += bytecode->co_argcount - nDefaultArgs - 1; + if (bytecode->co_argcount == 0 && isMethod) { + nNormalArgs += nDefaultArgs; + } else { + nNormalArgs += bytecode->co_argcount - nDefaultArgs - 1; + } if (bytecode->co_flags & CO_VARARGS) { varargs = true; } From 81c69150a65d662fdc50af1748d9f383534e5b31 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Wed, 11 Sep 2024 15:14:54 -0400 Subject: [PATCH 2/5] test for method missing self in JS call --- tests/python/test_functions_this.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/python/test_functions_this.py b/tests/python/test_functions_this.py index dc20f416..cbfd7157 100644 --- a/tests/python/test_functions_this.py +++ b/tests/python/test_functions_this.py @@ -202,3 +202,19 @@ def pyFunc(): pm.collect() # this should collect the JS proxy to pyFunc, which should decref pyFunc # pyFunc should be collected by now assert ref[0]() is None + + +def test_method_no_self(): + class What: + def some_method(): + return 3 + + obj = What() + + try: + pm.eval('x => x.some_method()')(obj) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__('TypeError:') + assert str(e).__contains__('takes 0 positional arguments but 1 was given') \ No newline at end of file From dc12241fb726c553c6a09b2e83fcd75c7054f51e Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Wed, 11 Sep 2024 15:37:36 -0400 Subject: [PATCH 3/5] test fix: more generic to work on Python < 3.12 --- tests/python/test_functions_this.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/python/test_functions_this.py b/tests/python/test_functions_this.py index cbfd7157..5f17c584 100644 --- a/tests/python/test_functions_this.py +++ b/tests/python/test_functions_this.py @@ -216,5 +216,4 @@ def some_method(): assert (False) except Exception as e: assert str(type(e)) == "" - assert str(e).__contains__('TypeError:') assert str(e).__contains__('takes 0 positional arguments but 1 was given') \ No newline at end of file From a72ae89d340f14b5431fce2541079a618047546a Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Wed, 11 Sep 2024 16:56:30 -0400 Subject: [PATCH 4/5] Update tests/python/test_functions_this.py Co-authored-by: Caleb Aikens --- tests/python/test_functions_this.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/python/test_functions_this.py b/tests/python/test_functions_this.py index 5f17c584..d9cbf528 100644 --- a/tests/python/test_functions_this.py +++ b/tests/python/test_functions_this.py @@ -214,6 +214,5 @@ def some_method(): try: pm.eval('x => x.some_method()')(obj) assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__('takes 0 positional arguments but 1 was given') \ No newline at end of file + except pm.SpiderMonkeyError as e: + assert 'takes 0 positional arguments but 1 was given' in str(e) \ No newline at end of file From ca1d37ca573b5843e8887a33f7dbd3b3004eb7d9 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Wed, 11 Sep 2024 16:57:46 -0400 Subject: [PATCH 5/5] logic simplification --- src/jsTypeFactory.cc | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/jsTypeFactory.cc b/src/jsTypeFactory.cc index 24eb5c4e..3e4f89d3 100644 --- a/src/jsTypeFactory.cc +++ b/src/jsTypeFactory.cc @@ -379,29 +379,21 @@ bool callPyFunc(JSContext *cx, unsigned int argc, JS::Value *vp) { else { nNormalArgs = 1; PyObject *f = pyFunc; - bool isMethod; if (PyMethod_Check(pyFunc)) { f = PyMethod_Function(pyFunc); // borrowed reference nNormalArgs -= 1; // don't include the implicit `self` of the method as an argument - isMethod = true; - } else { - isMethod = false; - } + } PyCodeObject *bytecode = (PyCodeObject *)PyFunction_GetCode(f); // borrowed reference PyObject *defaults = PyFunction_GetDefaults(f); // borrowed reference nDefaultArgs = defaults ? PyTuple_Size(defaults) : 0; - if (bytecode->co_argcount == 0 && isMethod) { - nNormalArgs += nDefaultArgs; - } else { - nNormalArgs += bytecode->co_argcount - nDefaultArgs - 1; - } + nNormalArgs += bytecode->co_argcount - nDefaultArgs - 1; if (bytecode->co_flags & CO_VARARGS) { varargs = true; } } // use faster calling if no arguments are needed - if (((nNormalArgs + nDefaultArgs) == 0 && !varargs)) { + if (((nNormalArgs + nDefaultArgs) <= 0 && !varargs)) { #if PY_VERSION_HEX >= 0x03090000 pyRval = PyObject_CallNoArgs(pyFunc); #else