Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PyTest failure in Python 3.11 and above caused by calling a method that throws in a generated class #1178

Open
Christopher-Chianelli opened this issue Mar 26, 2024 · 10 comments

Comments

@Christopher-Chianelli
Copy link
Contributor

Christopher-Chianelli commented Mar 26, 2024

Currently, to see results from PyTest failures caused by an Exception with a cause in Python 3.11 and above, I need to have this @JImplementationFor:

@JImplementationFor('java.lang.Throwable')
class _JavaException:
    @property
    def __cause__(self):
        return Exception()

To reproduce:

Python test:

def test_exception_with_cause():
    from org.example import Example
    consumer = example.getConsumer()
    consumer.accept('Error')

Java class:

package org.example

public class Example {
    private static final String className = "org.acme.GeneratedClass";
    private static final ClassLoader classLoader = new ClassLoader() {
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            if (name.equals(className)) {
                byte[] bytecode = getClassBytecode();
                return defineClass(name, bytecode, 0, bytecode.length);
            }
            return super.loadClass(name);
        }
    };

    private static byte[] getClassBytecode() {
        String className = "org.acme.GeneratedClass";
        String internalClassName = className.replace('.', '/');
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        classWriter.visit(Opcodes.V17, Modifier.PUBLIC, internalClassName, null, Type.getInternalName(Object.class), new String[] { Type.getInternalName(
                Consumer.class)});

        MethodVisitor methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE), null, null);
        methodVisitor.visitCode();
        methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
        methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "<init>", Type.getMethodDescriptor(Type.VOID_TYPE),
                false);
        methodVisitor.visitInsn(Opcodes.RETURN);
        methodVisitor.visitMaxs(0, 0);
        methodVisitor.visitEnd();

        methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "accept", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(
                Object.class)), null, null);
        methodVisitor.visitCode();
        methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
        methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
        methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(String.class));
        methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, internalClassName, "accept",
                Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)), false);
        methodVisitor.visitInsn(Opcodes.RETURN);
        methodVisitor.visitMaxs(0, 0);
        methodVisitor.visitEnd();


        // Generates throw new RuntimeException(message)
        methodVisitor = classWriter.visitMethod(Modifier.PUBLIC, "accept", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)), null, null);
        methodVisitor.visitCode();
        methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(RuntimeException.class));
        methodVisitor.visitInsn(Opcodes.DUP);
        methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
        methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(RuntimeException.class), "<init>",
                Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)), false);
        methodVisitor.visitInsn(Opcodes.ATHROW);
        methodVisitor.visitMaxs(0, 0);
        methodVisitor.visitEnd();

        classWriter.visitEnd();

        return classWriter.toByteArray();
    }
    public static Consumer<String> getConsumer() throws Throwable {
        @SuppressWarnings("unchecked")
        Class<? extends Consumer<String>> generatedClass = (Class<? extends Consumer<String>>) classLoader.loadClass(className);
        return generatedClass.getConstructor().newInstance();
    }
}

Exception from PyTest:

INTERNALERROR> Traceback (most recent call last):
INTERNALERROR>   File ".../_pytest/main.py", line 285, in wrap_session
INTERNALERROR>     session.exitstatus = doit(config, session) or 0
INTERNALERROR>                          ^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/main.py", line 339, in _main
INTERNALERROR>     config.hook.pytest_runtestloop(session=session)
INTERNALERROR>   File ".../pluggy/_hooks.py", line 501, in __call__
INTERNALERROR>     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
INTERNALERROR>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../pluggy/_manager.py", line 119, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
INTERNALERROR>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../pluggy/_callers.py", line 138, in _multicall
INTERNALERROR>     raise exception.with_traceback(exception.__traceback__)
INTERNALERROR>   File ".../pluggy/_callers.py", line 121, in _multicall
INTERNALERROR>     teardown.throw(exception)  # type: ignore[union-attr]
INTERNALERROR>     ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/logging.py", line 806, in pytest_runtestloop
INTERNALERROR>     return (yield)  # Run all the tests.
INTERNALERROR>             ^^^^^
INTERNALERROR>   File ".../pluggy/_callers.py", line 102, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>           ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/main.py", line 364, in pytest_runtestloop
INTERNALERROR>     item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
INTERNALERROR>   File ".../pluggy/_hooks.py", line 501, in __call__
INTERNALERROR>     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
INTERNALERROR>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../pluggy/_manager.py", line 119, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
INTERNALERROR>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../pluggy/_callers.py", line 138, in _multicall
INTERNALERROR>     raise exception.with_traceback(exception.__traceback__)
INTERNALERROR>   File ".../pluggy/_callers.py", line 121, in _multicall
INTERNALERROR>     teardown.throw(exception)  # type: ignore[union-attr]
INTERNALERROR>     ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/warnings.py", line 111, in pytest_runtest_protocol
INTERNALERROR>     return (yield)
INTERNALERROR>             ^^^^^
INTERNALERROR>   File ".../pluggy/_callers.py", line 121, in _multicall
INTERNALERROR>     teardown.throw(exception)  # type: ignore[union-attr]
INTERNALERROR>     ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/assertion/__init__.py", line 175, in pytest_runtest_protocol
INTERNALERROR>     return (yield)
INTERNALERROR>             ^^^^^
INTERNALERROR>   File ".../pluggy/_callers.py", line 121, in _multicall
INTERNALERROR>     teardown.throw(exception)  # type: ignore[union-attr]
INTERNALERROR>     ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/unittest.py", line 401, in pytest_runtest_protocol
INTERNALERROR>     res = yield
INTERNALERROR>           ^^^^^
INTERNALERROR>   File ".../pluggy/_callers.py", line 121, in _multicall
INTERNALERROR>     teardown.throw(exception)  # type: ignore[union-attr]
INTERNALERROR>     ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/faulthandler.py", line 85, in pytest_runtest_protocol
INTERNALERROR>     return (yield)
INTERNALERROR>             ^^^^^
INTERNALERROR>   File ".../pluggy/_callers.py", line 102, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>           ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/runner.py", line 115, in pytest_runtest_protocol
INTERNALERROR>     runtestprotocol(item, nextitem=nextitem)
INTERNALERROR>   File ".../_pytest/runner.py", line 134, in runtestprotocol
INTERNALERROR>     reports.append(call_and_report(item, "call", log))
INTERNALERROR>                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/runner.py", line 242, in call_and_report
INTERNALERROR>     report: TestReport = ihook.pytest_runtest_makereport(item=item, call=call)
INTERNALERROR>                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../pluggy/_hooks.py", line 501, in __call__
INTERNALERROR>     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
INTERNALERROR>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../pluggy/_manager.py", line 119, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
INTERNALERROR>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../pluggy/_callers.py", line 138, in _multicall
INTERNALERROR>     raise exception.with_traceback(exception.__traceback__)
INTERNALERROR>   File ".../pluggy/_callers.py", line 121, in _multicall
INTERNALERROR>     teardown.throw(exception)  # type: ignore[union-attr]
INTERNALERROR>     ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/tmpdir.py", line 317, in pytest_runtest_makereport
INTERNALERROR>     rep = yield
INTERNALERROR>           ^^^^^
INTERNALERROR>   File ".../pluggy/_callers.py", line 121, in _multicall
INTERNALERROR>     teardown.throw(exception)  # type: ignore[union-attr]
INTERNALERROR>     ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/skipping.py", line 268, in pytest_runtest_makereport
INTERNALERROR>     rep = yield
INTERNALERROR>           ^^^^^
INTERNALERROR>   File ".../pluggy/_callers.py", line 102, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>           ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/runner.py", line 367, in pytest_runtest_makereport
INTERNALERROR>     return TestReport.from_item_and_call(item, call)
INTERNALERROR>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/reports.py", line 364, in from_item_and_call
INTERNALERROR>     longrepr = item.repr_failure(excinfo)
INTERNALERROR>                ^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/python.py", line 1814, in repr_failure
INTERNALERROR>     return self._repr_failure_py(excinfo, style=style)
INTERNALERROR>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/nodes.py", line 464, in _repr_failure_py
INTERNALERROR>     return excinfo.getrepr(
INTERNALERROR>            ^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/_code/code.py", line 699, in getrepr
INTERNALERROR>     return fmt.repr_excinfo(self)
INTERNALERROR>            ^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/_code/code.py", line 1064, in repr_excinfo
INTERNALERROR>     reprtraceback = self.repr_traceback(excinfo_)
INTERNALERROR>                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/_code/code.py", line 993, in repr_traceback
INTERNALERROR>     entries = [
INTERNALERROR>               ^
INTERNALERROR>   File ".../_pytest/_code/code.py", line 994, in <listcomp>
INTERNALERROR>     self.repr_traceback_entry(entry, excinfo if last == entry else None)
INTERNALERROR>   File ".../_pytest/_code/code.py", line 948, in repr_traceback_entry
INTERNALERROR>     reprfileloc = ReprFileLocation(path, entry.lineno + 1, message)
INTERNALERROR>                                          ^^^^^^^^^^^^
INTERNALERROR>   File ".../_pytest/_code/code.py", line 212, in lineno
INTERNALERROR>     return self._rawentry.tb_lineno - 1
INTERNALERROR>            ~~~~~~~~~~~~~~~~~~~~~~~~~^~~
INTERNALERROR> TypeError: unsupported operand type(s) for -: 'NoneType' and 'int'
@Thrameos
Copy link
Contributor

Thrameos commented Mar 26, 2024 via email

@Thrameos
Copy link
Contributor

I was unable to replicate the issue from your example. I get

______________________________________________ test_exception_with_cause _______________________________________________
java.lang.java.lang.IllegalStateException: java.lang.IllegalStateException: cause

The above exception was the direct cause of the following exception:

>   throw new IllegalStateException("test", new IllegalStateException("cause"));
E   Exception: Java Exception

Example.java:4: Exception

The above exception was the direct cause of the following exception:

    def test_exception_with_cause():
        jpype.startJVM(classpath='.')
        example = jpype.JClass('Example')
>       example.throwingMethod()
E       java.lang.java.lang.IllegalStateException: java.lang.IllegalStateException: test

test.py:5: java.lang.IllegalStateException

@Christopher-Chianelli
Copy link
Contributor Author

At this point, I am almost certain this is a pytest bug, since this code still cause the internal error:

def test_reproducer():
    try:
        code_that_cause_exception()
    except:
        raise Exception

@Christopher-Chianelli
Copy link
Contributor Author

Forgot that raise Exception is a short for raise Exception from last_raise_exception. Managed to get a minimal reproducer; it has to do with generated classes;

Reproducer is https://github.com/Christopher-Chianelli/issue-reproducer/tree/jpype-1178

@Christopher-Chianelli Christopher-Chianelli changed the title PyTest failure in Python 3.11 and above caused by non-null __cause__ attribute PyTest failure in Python 3.11 and above caused by calling a method that throws in a generated class Apr 1, 2024
@Christopher-Chianelli
Copy link
Contributor Author

If the generated class has source information
(i.e. classWriter.visitSource("MyFile.py", "debug"); and methodVisitor.visitLineNumber(0, line);),
then the exception does not occur.

@Thrameos
Copy link
Contributor

Thrameos commented Apr 2, 2024

Unfortunately I got

[INFO] Scanning for projects...
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[FATAL] Non-resolvable parent POM for org.optaplanner:DroolsExecutableReproducer:8.0.0-SNAPSHOT: Could not find artifact org.optaplanner:optaplanner-build-parent:pom:8.0.0-SNAPSHOT and 'parent.relativePath' points at wrong local POM @ line 7, column 11
 @
[ERROR] The build could not read 1 project -> [Help 1]
[ERROR]
[ERROR]   The project org.optaplanner:DroolsExecutableReproducer:8.0.0-SNAPSHOT (/mnt/c/Users/nelson85/Documents/devel/open/jpype/issue/pom.xml) has 1 error
[ERROR]     Non-resolvable parent POM for org.optaplanner:DroolsExecutableReproducer:8.0.0-SNAPSHOT: Could not find artifact org.optaplanner:optaplanner-build-parent:pom:8.0.0-SNAPSHOT and 'parent.relativePath' points at wrong local POM @ line 7, column 11 -> [Help 2]
[ERROR]

So I was unable to build the reproducer to verify the test. Sorry! I will try again next weekend to see if I can sort out what is going on. Unfortunately, as I don't use maven it doesn't make much sense to me.

The specific call where we mess with linenum is in native/common/jp_exception.cpp

PyObject *tb_create(
        PyObject *last_traceback,
        PyObject *dict,
        const char* filename,
        const char* funcname,
        int linenum)
{
    // Create a code for this frame. (ref count is 1)
    JPPyObject code = JPPyObject::accept((PyObject*)PyCode_NewEmpty(filename, funcname, linenum));

    // If we don't get the code object there is no point
    if (code.get() == nullptr)
        return nullptr;

    // Create a frame for the traceback.
    PyThreadState *state = PyThreadState_GET();
    PyFrameObject *pframe = PyFrame_New(state, (PyCodeObject*) code.get(), dict, NULL);
    JPPyObject frame = JPPyObject::accept((PyObject*)pframe);

    // If we don't get the frame object there is no point
    if (frame.get() == nullptr)
        return nullptr;

    // Create a traceback
#if PY_MINOR_VERSION<11
    JPPyObject lasti = JPPyObject::claim(PyLong_FromLong(pframe->f_lasti));
#else
    JPPyObject lasti = JPPyObject::claim(PyLong_FromLong(PyFrame_GetLasti(pframe)));
#endif
    JPPyObject linenuma = JPPyObject::claim(PyLong_FromLong(linenum));
    JPPyObject tuple = JPPyObject::call(PyTuple_Pack(4, Py_None, frame.get(), lasti.get(), linenuma.get()));
    JPPyObject traceback = JPPyObject::accept(PyObject_Call((PyObject*) &PyTraceBack_Type, tuple.get(), NULL));

    // We could fail in process
    if (traceback.get() == nullptr)
    {
        return nullptr;
    }

    return traceback.keep();
}

As you can see we are calling the constructor with a valid traceback linenum. If there is a problem then most likely traceback changed its signature starting in 3.11 and we would need to adjust the constructor call to account for it. I scanned the Python code base and don't see an errors in the handoff (though admittedly the code is very ugly....)

    PyObject *argsbuf[4];
    PyObject * const *fastargs;
    Py_ssize_t nargs = PyTuple_GET_SIZE(args);
    PyObject *tb_next;
    PyFrameObject *tb_frame;
    int tb_lasti;
    int tb_lineno;

    fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 4, 4, 0, argsbuf);
    if (!fastargs) {
        goto exit;
    }
    tb_next = fastargs[0];
    if (!PyObject_TypeCheck(fastargs[1], &PyFrame_Type)) {
        _PyArg_BadArgument("TracebackType", "argument 'tb_frame'", (&PyFrame_Type)->tp_name, fastargs[1]);
        goto exit;
    }
    tb_frame = (PyFrameObject *)fastargs[1];
    tb_lasti = _PyLong_AsInt(fastargs[2]);
    if (tb_lasti == -1 && PyErr_Occurred()) {  <===  BAD THING HAPPENS HERE IF THERE IS ALREADY AN EXCEPTION 
        goto exit;
    }
    tb_lineno = _PyLong_AsInt(fastargs[3]);
    if (tb_lineno == -1 && PyErr_Occurred()) {
        goto exit;
    }
    return_value = tb_new_impl(type, tb_next, tb_frame, tb_lasti, tb_lineno);

As you can see form this code it may be the same error as in the PR. There is a potential the lost exception is tripping a problem in the copied traceback. But if that was the case the traceback would have failed without producing, and not one with Py_None in the linenum slot. Thus I still don't have a good explanation for what could be happening here.

@Christopher-Chianelli
Copy link
Contributor Author

I think you checked out the wrong branch; the branch should be jpype-1178 not drools-classloader-reproducer (which is the default branch)

@Thrameos
Copy link
Contributor

Thrameos commented Apr 2, 2024

Ah... will try again later them.

@Thrameos
Copy link
Contributor

Thrameos commented Apr 2, 2024

I got


(python3.12-wsl) nelson85@wl-5520983:~/devel/open/jpype/issue$ pytest
======================================================================================= test session starts ========================================================================================
platform linux -- Python 3.12.0, pytest-8.1.1, pluggy-1.4.0 -- /home/nelson85/env/python3.12-wsl/bin/python
cachedir: .pytest_cache
rootdir: /mnt/c/Users/nelson85/Documents/devel/open/jpype
configfile: setup.cfg
collected 2 items

tests/test_reproducer.py::test_throwing FAILED                                                                                                                                               [ 50%]
tests/test_reproducer.py::test_b PASSED                                                                                                                                                      [100%]

============================================================================================= FAILURES =============================================================================================
__________________________________________________________________________________________ test_throwing ___________________________________________________________________________________________

>   ???
E   Exception: Java Exception

org.acme.GeneratedClass.java:-1: Exception

The above exception was the direct cause of the following exception:

    def test_throwing():
        import jpype
        import jpype.imports
        jpype.startJVM(classpath=[
            'target/issue-reproducer-1.0.0-SNAPSHOT.jar',
            'target/dependency/asm-9.7.jar'
        ])
        from org.acme import MyClass
        consumer = MyClass.getConsumer()
>       consumer.accept('My Error')
E       java.lang.java.lang.RuntimeException: java.lang.RuntimeException: My Error

tests/test_reproducer.py:10: java.lang.RuntimeException
===================================================================================== short test summary info ======================================================================================
FAILED tests/test_reproducer.py::test_throwing - java.lang.java.lang.RuntimeException: java.lang.RuntimeException: My Error
=================================================================================== 1 failed, 1 passed in 0.49s ====================================================================================

Is this correct or incorrect?

@Christopher-Chianelli
Copy link
Contributor Author

This is odd,

I don't know why it randomly worked then failed after a mvn clean install.

This is what it looks like when the internal error happens:

mvn clean install
python -m venv venv
. venv/bin/activate
pip install JPype1 pytest
pytest
===================================================================================== test session starts ======================================================================================
platform linux -- Python 3.12.2, pytest-8.1.1, pluggy-1.4.0
rootdir: .../issue-reproducer
collected 2 items                                                                                                                                                                              

tests/test_reproducer.py 
INTERNALERROR> Traceback (most recent call last):
...

Note the second test is not even run.

Christopher-Chianelli added a commit to TimefoldAI/timefold-solver-python that referenced this issue Apr 2, 2024
* Python 3.12 changed the meaning of the arg for both COMPARE_OP
      and JUMP instructions:
    
    * For COMPARE_OP, the index now starts at the fifth-lowest bit.
    * For JUMP instructions,
       "The argument of a jump is the offset of the target instruction relative to the instruction that appears immediately after the jump instruction’s CACHE entries.
       As a consequence, the presence of the CACHE instructions is transparent for forward jumps but needs to be taken into account when reasoning about backward jumps."
    
      For both of these, we parse the argrepr. In particular:
    
    * For COMPARE_OP, it be "<", ">", "==", "!=", ">=" or "<=".
    * For JUMP instructions, it be "to BYTECODE_OFFSET", which is exactly
       twice the instruction index.
    
* Python 3.12 changed the meaning of FOR_ITER to leave the iterator on
    the stack (and push an undocumented object) when exhausted,
    and added the instruction END_FOR to pop them both off. However, since
    the target for FOR_ITER is always END_FOR (see https://github.com/python/cpython/blob/17a82a1d16a46b6c008240bcc698619419ce5554/Python/bytecodes.c#L2289-L2293),
   we can "optimize" it by using the old implementation and making END_FOR a no-op.
    
* Python 3.12 merged LOAD_ATTR and LOAD_METHOD into one opcode.
    
* Python 3.12 added BINARY_SLICE and STORE_SLICE opcodes to implement
      `a[start:end]` and `a[start:end] = iterable` (previously,
      BUILD_SLICE was used followed by `GET_ITEM` or `SET_ITEM`).
    
* Python 3.12 added END_SEND for generator cleanup, which we
      implement as a NOOP.
    
* Python 3.12 added CLEANUP_THROW and INTRINSIC_STOPITERATION_ERROR,
      which replace TOS with its value if TOS a StopIteration,
      else reraise the error on TOS. CLEANUP_THROW additionally pops
      two additional values off the stack.
    
* Python 3.12 added LOAD_FAST_CHECK and LOAD_FAST_AND_CLEAR.
      LOAD_FAST now will never raise unbound local error,
      LOAD_FAST_CHECK might raise an unbound local error,
      and LOAD_FAST_AND_CLEAR will load NONE when the local is unbound
      (and additionally sets the local to null).
    
      Since Java rightfully says "???" when it sees code that can access
      an unbound local, we need to set locals used by
      LOAD_FAST_AND_CLEAR to null before generating code
      for opcodes.
    
* Python 3.12 added RETURN_CONST to return a constant.
    
* Python 3.12 changed UNARY_POS from a "fast" opcode to a
      slow "intrinsic" opcode since "+a" is rarely used. This
      does not affect us (other than having a new alternative
      way of writing "UNARY_POS" as
      "INTRINSIC_1_INTRINSIC_UNARY_POSITIVE".
    
* Simplified and fix some bugs in PythonSlice code
    
* Add a workaround for jpype-project/jpype#1178
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants