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

Subclassing a #[pyclass] in Python causes segfault during interpreter finalization #220

Closed
joar opened this issue Sep 12, 2018 · 11 comments · Fixed by #721
Closed

Subclassing a #[pyclass] in Python causes segfault during interpreter finalization #220

joar opened this issue Sep 12, 2018 · 11 comments · Fixed by #721

Comments

@joar
Copy link
Contributor

joar commented Sep 12, 2018

🐛 Bug Reports

When reporting a bug, please provide the following information. If this is not a bug report you can just discard this template.

🌍 Environment

  • Your operating system and version: Ubuntu 18.04
  • Your python version: Python 3.7
  • How did you install python (e.g. apt or pyenv)? Did you use a virtualenv?:
    • Python installed through the "deadsnakes" PPA
    • I use python's venv
  • Your rust version (rustc --version):
    rustc 1.30.0-nightly (2d4e34ca8 2018-09-09)

💥 Reproducing

Check out the branch at https://github.com/joar/pyo3/tree/bug/subclass-causes-sigsegv/examples/word-count

$ cd examples/word-count

$ python setup.py develop

$ cat ../../target/debug/build/pyo3-4833970e0e0d17d0/output
cargo:rustc-cfg=Py_3_5
cargo:rustc-cfg=Py_3_6
cargo:rustc-cfg=Py_3_7
cargo:rustc-cfg=Py_3
cargo:rustc-cfg=py_sys_config="WITH_THREAD"
cargo:python_flags=FLAG_WITH_THREAD=1,CFG_Py_3_5,CFG_Py_3_6,CFG_Py_3_7
cargo:rerun-if-env-changed=LD_LIBRARY_PATH
cargo:rerun-if-env-changed=PATH
cargo:rerun-if-env-changed=PYTHON_SYS_EXECUTABLE
cargo:rerun-if-env-changed=LIB

$ python minimal-test.py
===> creating WordCounter
===> created WordCounter
===> deleting wc
===> deleted wc
fish: “python minimal-test.py” terminated by signal SIGSEGV (Address boundary error)

I believe the issue occurs during python's final GC.

@konstin
Copy link
Member

konstin commented Sep 13, 2018

Thanks for the report!

I've minimized the example:

#[pyclass(subclass)]
struct SomeClass {}

#[pymethods]
impl SomeClass {
    #[new]
    fn __new__(obj: &PyRawObject) -> PyResult<()> {
        obj.init(|_| SomeClass {})
    }
}

#[pymodinit]
fn word_count(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<SomeClass>()?;
    Ok(())
}
from word_count import SomeClass


class SomeSubClass(SomeClass):
    pass


a = SomeSubClass()
print(a)

With python-dbg you get a bit more information:

<__main__.SomeSubClass object at 0x7ffff6820f60>
Debug memory block at address p=0x7ffff6820f60: API '�'
    6392409932133498880 bytes originally requested
    The 7 pad bytes at p-7 are not all FORBIDDENBYTE (0xfb):
        at p-7: 0xff *** OUCH
        at p-6: 0xff *** OUCH
        at p-5: 0xff *** OUCH
        at p-4: 0xff *** OUCH
        at p-3: 0xff *** OUCH
        at p-2: 0xff *** OUCH
        at p-1: 0xff *** OUCH
    Because memory is corrupted at the start, the count of bytes requested
       may be bogus, and checking the trailing pad bytes may segfault.
    The 8 pad bytes at tail=0x58b6e6f6f6010f60 are 
Program received signal SIGSEGV, Segmentation fault.
0x000000000042163f in _PyObject_DebugDumpAddress (p=p@entry=0x7ffff6820f60) at ../Objects/obmalloc.c:2108
2108	../Objects/obmalloc.c: Datei oder Verzeichnis nicht gefunden.

And with gdb I could get a backtrace:

(gdb) bt
#0  0x000000000042163f in _PyObject_DebugDumpAddress (p=p@entry=0x7ffff6820f60) at ../Objects/obmalloc.c:2108
#1  0x0000000000421989 in _PyMem_DebugCheckAddress (api=<optimized out>, p=p@entry=0x7ffff6820f60) at ../Objects/obmalloc.c:2049
#2  0x0000000000421a00 in _PyMem_DebugRawFree (ctx=ctx@entry=0x9c3400 <_PyMem_Debug+96>, p=p@entry=0x7ffff6820f60) at ../Objects/obmalloc.c:1905
#3  0x0000000000421a4f in _PyMem_DebugFree (ctx=0x9c3400 <_PyMem_Debug+96>, ptr=0x7ffff6820f60) at ../Objects/obmalloc.c:1986
#4  0x0000000000422909 in PyObject_Free (ptr=<optimized out>) at ../Objects/obmalloc.c:503
#5  0x00007ffff53df182 in <T as pyo3::typeob::PyObjectAlloc<T>>::dealloc (py=..., obj=0x7ffff6820f60) at /home/konsti/pyo3/src/typeob.rs:239
#6  0x00007ffff53fcab0 in pyo3::typeob::tp_dealloc_callback (obj=0x7ffff6820f60) at /home/konsti/pyo3/src/typeob.rs:463
#7  0x00000000004b6518 in subtype_dealloc (self=self@entry=<SomeSubClass at remote 0x7ffff6820f60>) at ../Objects/typeobject.c:1222
#8  0x00000000004a4b23 in _Py_Dealloc (op=<SomeSubClass at remote 0x7ffff6820f60>) at ../Objects/object.c:1791
#9  0x0000000000491101 in free_keys_object (keys=keys@entry=0xb97a30) at ../Objects/dictobject.c:561
#10 0x0000000000492d8f in dict_dealloc (mp=mp@entry=0x7ffff7efb058) at ../Objects/dictobject.c:2024
#11 0x00000000004a4b23 in _Py_Dealloc (
    op={'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <unknown at remote 0x7ffff7e7cbb8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module at remote 0x7ffff7f54cd8>, '__cached__': None, 'tempfile': <module at remote 0x7ffff681b8d8>, 'SomeClass': <type at remote 0x7ffff56c41d8>, 'SomeSubClass': <type at remote 0xbc4258>, <unknown at remote 0x7ffff682a3c0>: <unknown at remote 0x7ffff6820e90>, 'a': <SomeSubClass at remote 0x7ffff6820f60>}) at ../Objects/object.c:1791
#12 0x00000000004a224d in module_dealloc (m=m@entry=0x7ffff7e956d8) at ../Objects/moduleobject.c:643
#13 0x00000000004a4b23 in _Py_Dealloc (op=op@entry=<module at remote 0x7ffff7e956d8>) at ../Objects/object.c:1791
#14 0x0000000000494cf8 in insertdict (mp=mp@entry=0x7ffff7f56b20, key=key@entry='__main__', hash=<optimized out>, value=value@entry=None) at ../Objects/dictobject.c:1181
#15 0x00000000004977b4 in PyDict_SetItem (
    op=op@entry={'builtins': None, 'sys': None, '_frozen_importlib': None, '_imp': None, '_warnings': None, '_thread': None, '_weakref': None, '_frozen_importlib_external': None, '_io': None, 'marshal': None, 'posix': None, 'zipimport': None, 'encodings': None, 'codecs': None, '_codecs': None, 'encodings.aliases': None, 'encodings.utf_8': None, '_signal': None, '__main__': None, 'encodings.latin_1': <module at remote 0x7ffff7ec6b58>, 'io': <module at remote 0x7ffff7e631d8>, 'abc': <module at remote 0x7ffff7e633d8>, '_weakrefset': <module at remote 0x7ffff7e63a58>, 'site': <module at remote 0x7ffff7e80658>, 'os': <module at remote 0x7ffff7e80cd8>, 'errno': <module at remote 0x7ffff7e1f158>, 'stat': <module at remote 0x7ffff7e1f258>, '_stat': <module at remote 0x7ffff7e1f7d8>, 'posixpath': <module at remote 0x7ffff7e1f2d8>, 'genericpath': <module at remote 0x7ffff7e2f158>, 'os.path': <module at remote 0x7ffff7e1f2d8>, '_collections_abc': <module at remote 0x7ffff7e2f0d8>, '_sitebuiltins': <module at remote 0x7ffff7e80e58>, '_b...(truncated), key='__main__', value=None) at ../Objects/dictobject.c:1575
#16 0x000000000055cbf0 in PyImport_Cleanup () at ../Python/import.c:402
#17 0x0000000000424e26 in Py_FinalizeEx () at ../Python/pylifecycle.c:608
#18 0x000000000043bb49 in Py_Main (argc=argc@entry=2, argv=argv@entry=0xa91260) at ../Modules/main.c:830
#19 0x000000000042111f in main (argc=2, argv=0x7fffffffd9c8) at ../Programs/python.c:69

@konstin konstin added the bug label Sep 13, 2018
joar added a commit to joar/rust-csv-py that referenced this issue Sep 17, 2018
Until PyO3/pyo3#220 is sorted out
@joar
Copy link
Contributor Author

joar commented Nov 13, 2018

@konstin Awesome! Thank you!

@joar
Copy link
Contributor Author

joar commented Dec 10, 2018

@konstin I'm sorry, but it does not seem like this issue is fixed. Running examples/rustapi_module/tests/test_subclassing.py fails for me.

joar added a commit to joar/rust-csv-py that referenced this issue Dec 10, 2018
PyO3/pyo3#220 is not fixed yet.
@konstin
Copy link
Member

konstin commented Dec 15, 2018

:/

@konstin konstin reopened this Dec 15, 2018
@kngwyu kngwyu assigned kngwyu and unassigned kngwyu Dec 15, 2018
@kngwyu
Copy link
Member

kngwyu commented Dec 15, 2018

I can't reproduce 🤔

@konstin
Copy link
Member

konstin commented Dec 15, 2018

@joar Did you update to the latest master?

@joar
Copy link
Contributor Author

joar commented Jan 9, 2019

@konstin I'm pretty sure I did. Here's how I reproduce the error:

reproducing segfault during GC of a subclass of a pyo3 class

Extracted and polished commands & output from the terminal recording:

$ python --version 
Python 3.7.1
$ cargo version                            
cargo 1.32.0-nightly (5e85ba14a 2018-12-02)
$ rustc --version                          
rustc 1.32.0-nightly (4a45578bc 2018-12-07)
$ cd rust-csv-py/pyo3 # I'm using the submodule checkout of pyo3 inside my rust-csv-py repo
$ git pull
Updating 26b88ca2..1b4afd11
[...]
$ cd examples/rustapi_module
$ python setup.py develop
[...]
$ python tests/test_subclassing.py
/home/joar/.virtualenvs/rust-csv-py/lib/python3.7/site.py:165: DeprecationWarning: 'U' mode is deprecated
  f = open(fullname, "rU")
Debug memory block at address p=0x7f620692bc20: API '�'
    5791237713491329024 bytes originally requested
    The 7 pad bytes at p-7 are not all FORBIDDENBYTE (0xfb):
        at p-7: 0xff *** OUCH
        at p-6: 0xff *** OUCH
        at p-5: 0xff *** OUCH
        at p-4: 0xff *** OUCH
        at p-3: 0xff *** OUCH
        at p-2: 0xff *** OUCH
        at p-1: 0xff *** OUCH
    Because memory is corrupted at the start, the count of bytes requested
       may be bogus, and checking the trailing pad bytes may segfault.
    The 8 pad bytes at tail=0x505f1b666911bc20 are fish: “python tests/test_subclassing.py” terminated by signal SIGSEGV (Address boundary error)
$ gdb -ex r --args (which python) tests/test_subclassing.py 
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /home/joar/.virtualenvs/rust-csv-py/bin/python...done.
Starting program: /home/joar/.virtualenvs/rust-csv-py/bin/python tests/test_subclassing.py
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
/home/joar/.virtualenvs/rust-csv-py/lib/python3.7/site.py:165: DeprecationWarning: 'U' mode is deprecated
  f = open(fullname, "rU")
Debug memory block at address p=0x7ffff7e72c20: API '�'
    6925957840821485568 bytes originally requested
    The 7 pad bytes at p-7 are not all FORBIDDENBYTE (0xfb):
        at p-7: 0xff *** OUCH
        at p-6: 0xff *** OUCH
        at p-5: 0xff *** OUCH
        at p-4: 0xff *** OUCH
        at p-3: 0xff *** OUCH
        at p-2: 0xff *** OUCH
        at p-1: 0xff *** OUCH
    Because memory is corrupted at the start, the count of bytes requested
       may be bogus, and checking the trailing pad bytes may segfault.
    The 8 pad bytes at tail=0x601e71f5f7662c20 are 
Program received signal SIGSEGV, Segmentation fault.
0x000055555560ede2 in _PyObject_DebugDumpAddress (p=p@entry=0x7ffff7e72c20)
    at ../Objects/obmalloc.c:2340
2340	../Objects/obmalloc.c: No such file or directory.
(gdb) bt
#0  0x000055555560ede2 in _PyObject_DebugDumpAddress (p=p@entry=0x7ffff7e72c20)
    at ../Objects/obmalloc.c:2340
#1  0x000055555560f14a in _PyMem_DebugCheckAddress (api=<optimized out>, 
    p=p@entry=0x7ffff7e72c20) at ../Objects/obmalloc.c:2281
#2  0x000055555560f1c7 in _PyMem_DebugRawFree (
    ctx=ctx@entry=0x555555bddc60 <_PyMem_Debug+96>, p=p@entry=0x7ffff7e72c20)
    at ../Objects/obmalloc.c:2092
#3  0x000055555560f20f in _PyMem_DebugFree (
    ctx=0x555555bddc60 <_PyMem_Debug+96>, ptr=0x7ffff7e72c20)
    at ../Objects/obmalloc.c:2217
#4  0x0000555555610471 in PyObject_Free (ptr=<optimized out>)
    at ../Objects/obmalloc.c:640
#5  0x00007ffff5b6efa3 in pyo3::typeob::PyObjectAlloc::dealloc (py=..., 
    obj=0x7ffff7e72c20) at /home/joar/git/rust-csv-py/pyo3/src/typeob.rs:227
#6  0x00007ffff5b62d60 in pyo3::typeob::tp_dealloc_callback (
    obj=0x7ffff7e72c20) at /home/joar/git/rust-csv-py/pyo3/src/typeob.rs:461
#7  0x0000555555621f20 in subtype_dealloc (self=self@entry=0x7ffff7e72c20)
    at ../Objects/typeobject.c:1256
#8  0x000055555560b7ed in _Py_Dealloc (op=0x7ffff7e72c20)
    at ../Objects/object.c:1934
#9  0x00005555555f8a6b in free_keys_object (keys=keys@entry=0x555555d12c70)
    at ../Objects/dictobject.c:559
#10 0x00005555555fa8e3 in dict_dealloc (mp=mp@entry=0x7ffff7eaa328)
---Type <return> to continue, or q <return> to quit---
    at ../Objects/dictobject.c:1913
#11 0x000055555560b7ed in _Py_Dealloc (op=0x7ffff7eaa328)
    at ../Objects/object.c:1934
#12 0x000055555560944e in module_dealloc (m=m@entry=0x7ffff7ea57d8)
    at ../Objects/moduleobject.c:684
#13 0x000055555560b7ed in _Py_Dealloc (op=0x7ffff7ea57d8)
    at ../Objects/object.c:1934
#14 0x00005555555fcf95 in insertdict (mp=mp@entry=0x7ffff7f56e68, 
    key=key@entry=0x7ffff7eb2580, hash=1016438506669533865, 
    value=value@entry=0x555555bdd520 <_Py_NoneStruct>)
    at ../Objects/dictobject.c:1076
#15 0x00005555555fe5da in PyDict_SetItem (op=0x7ffff7f56e68, 
    key=0x7ffff7eb2580, value=0x555555bdd520 <_Py_NoneStruct>)
    at ../Objects/dictobject.c:1463
#16 0x00005555555ff174 in dict_ass_sub (mp=<optimized out>, v=<optimized out>, 
    w=<optimized out>) at ../Objects/dictobject.c:2056
#17 0x00005555557c656b in PyObject_SetItem (o=o@entry=0x7ffff7f56e68, 
    key=<optimized out>, value=0x555555bdd520 <_Py_NoneStruct>)
    at ../Objects/abstract.c:198
#18 0x00005555556b0c2e in PyImport_Cleanup () at ../Python/import.c:479
#19 0x00005555556bfa73 in Py_FinalizeEx () at ../Python/pylifecycle.c:1192
#20 0x00005555555c4e9e in pymain_main (pymain=pymain@entry=0x7fffffffd660)
    at ../Modules/main.c:2789
---Type <return> to continue, or q <return> to quit---
#21 0x00005555555c4fb1 in _Py_UnixMain (argc=<optimized out>, 
    argv=<optimized out>) at ../Modules/main.c:2822
#22 0x00005555555bfb73 in main (argc=<optimized out>, argv=<optimized out>)
    at ../Programs/python.c:15
(gdb) quit
A debugging session is active.

	Inferior 1 [process 22416] will be killed.

Quit anyway? (y or n) y

@joar
Copy link
Contributor Author

joar commented Jan 9, 2019

EDIT: I opened #322 that should fix this
I was looking at the testing setup in order to figure out if the subclassing test was subject to CI. I think I've found an issue with the testing setup: The --workdir argument seems to be used as if it would cause tox to read the tox.ini from the path given as argument via --workdir:

tox -e py --workdir $example

However, the --workdir does not seem to work that way - It seems that the root tox.ini is executed for each item in examples, rather than each item's tox.ini.

The root tox.ini has commands = cargo test, and sure enough, if you search for test result: ok. 10 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out on https://travis-ci.org/PyO3/pyo3/jobs/477005838 it occurs 3 times:

  • 1 time for the cargo test on
    cargo test --features $FEATURES
  • 2 times via tox for each item in examples/

@joar
Copy link
Contributor Author

joar commented Jan 9, 2019

There's another problem with the tests even once I've fixed the issue in #220 (comment).

It seems that tox is hiding the sigsegv that occurs in pytest:
asciicast

@pganssle
Copy link
Member

Should this be closed now that #322 has been merged?

@joar
Copy link
Contributor Author

joar commented Feb 27, 2019

@pganssle No, I don't believe so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants