wlav / cppyy

Other
407 stars 42 forks source link

Stack overflow when calling private constructor #40

Closed saraedum closed 2 years ago

saraedum commented 2 years ago

Consider the following code snippet:

import cppyy
cppyy.cppdef(r"""
#include <boost/operators.hpp>
#include <atomic>

class X : boost::equality_comparable<X> {
    X();
    mutable std::atomic<size_t> x;
};
""")
cppyy.gbl.X()

This crashes for me with:

Fatal Python error: Cannot recover from stack overflow.

Current thread 0x00007faa59ead740 (most recent call first):
  File "../e-antic/crash.py", line 11 in <module>

Program received signal SIGABRT, Aborted.

Debugging with GDB shows the following infinite recursion:

#29 0x0000557ed94d1d25 in PyObject_Call (callable=<X_meta(__module__='__main__', __doc__=None) at remote 0x557edd549990>, args=(), kwargs=0x0)
    at /home/conda/feedstock_root/build_artifacts/python_1635226063427/work/Objects/call.c:245
#30 0x00007faa501535a7 in CPyCppyy::CPPConstructor::Call(CPyCppyy::CPPInstance*&, _object*, unsigned long, _object*, CPyCppyy::CallContext*) ()
   from /tmp/ruth/micromamba/envs/e-antic-build/lib/python3.7/site-packages/libcppyy.cpython-37m-x86_64-linux-gnu.so
#31 0x00007faa501314e6 in CPyCppyy::(anonymous namespace)::mp_call(CPyCppyy::CPPOverload*, _object*, _object*) ()
   from /tmp/ruth/micromamba/envs/e-antic-build/lib/python3.7/site-packages/libcppyy.cpython-37m-x86_64-linux-gnu.so
#32 0x0000557ed94d1d25 in PyObject_Call (callable=<cppyy.CPPOverload at remote 0x7faa4f608af0>, args=(), kwargs=0x0) at /home/conda/feedstock_root/build_artifacts/python_1635226063427/work/Objects/call.c:245
#33 0x0000557ed95eafee in slot_tp_init (self=<X at remote 0x7faa4f608aa0>, args=(), kwds=0x0) at /home/conda/feedstock_root/build_artifacts/python_1635226063427/work/Objects/typeobject.c:6639
#34 0x0000557ed956d2ea in type_call (type=<optimized out>, args=(), kwds=0x0) at /home/conda/feedstock_root/build_artifacts/python_1635226063427/work/Objects/typeobject.c:971
#35 0x0000557ed94d1d25 in PyObject_Call (callable=<X_meta(__module__='__main__', __doc__=None) at remote 0x557edd549990>, args=(), kwargs=0x0)
    at /home/conda/feedstock_root/build_artifacts/python_1635226063427/work/Objects/call.c:245

I don’t except calling a private constructor to actually yield an instance of the object so this should be an error. However, this problem shows up when cppyy attempts to convert arguments, i.e., when invoking an overloaded function with some argument that is not X:

void f(const X&);
void f(something else);

Conversion to X is apparently attempted when invoking say f([]) which then leads to the above crash.

This happened on Linux with the latest cppyy from conda-forge:

cppyy                     2.2.0            py37ha1a9abc_1    conda-forge
cppyy-backend             1.14.7           py37h2527ec5_0    conda-forge
cppyy-cling               6.25.2           py37hfa6fd77_1    conda-forge
cpycppyy                  1.12.8           py37h2527ec5_1    conda-forge
wlav commented 2 years ago

Thanks for the nice reproducer! Here's a simpler one:

class Z {
public:
    virtual ~Z() {}
};

class X : Z {
    X();
    X(const X&&) = delete;
};

It's an odd combo of different errors: to only truly have no contructors (not even Cling-provided faux default ones), the move and/or copy constructor needs to be deleted (that's the contribution from std::atomic<size_t> in the original reproducer). I figured it's a bug that upstream still provides the move constructor in the reflection info otherwise, but that's actually proper per C++ rules (and they are provided by Clang, not Cling).

Then the derivation from the base Z without an __init__ in X, makes Python call Z's __init__, which resolves on the cppyy side as if X were a Python-side derived class without provided __init__. It then proceeds to call the expected internal dispatcher constructor. However, since it's not a Python class, there is no dispatcher, and the contructor found is again the Z base class one. Hence infinite recursion. Cute.

The cppyy side is now fixed in repo (preventing the infinite recursion): both a check for __init__ belonging to the proper class on call, and a proper error for the case where there are no (public) constructors available. Tests are in, including a test for the case of a protected constructor and Python-side derivation.

wlav commented 2 years ago

Fix released with 2.3.0 and its dependencies.