Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/CPPMethod.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -968,8 +968,7 @@ PyObject* CPyCppyy::CPPMethod::Execute(void* self, ptrdiff_t offset, CallContext
// call the interface method
PyObject* result = 0;

if (CallContext::sSignalPolicy != CallContext::kProtected && \
!(ctxt->fFlags & CallContext::kProtected)) {
if (!(CallContext::GlobalPolicyFlags() & CallContext::kProtected) && !(ctxt->fFlags & CallContext::kProtected)) {
// bypasses try block (i.e. segfaults will abort)
result = ExecuteFast(self, offset, ctxt);
} else {
Expand Down
43 changes: 13 additions & 30 deletions src/CPPOverload.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -495,37 +495,23 @@ static int mp_setcreates(CPPOverload* pymeth, PyObject* value, void*)
return set_flag(pymeth, value, CallContext::kIsCreator, "__creates__");
}

constexpr const char *mempolicy_error_message =
"The __mempolicy__ attribute can't be used, because in the past it was reserved to manage the local memory policy. "
"If you want to do that now, please implement a pythonization for your class that uses SetOwnership() to manage the "
"ownership of arguments according to your needs.";

//----------------------------------------------------------------------------
static PyObject* mp_getmempolicy(CPPOverload* pymeth, void*)
static PyObject* mp_getmempolicy(CPPOverload*, void*)
{
// Get '_mempolicy' enum, which determines ownership of call arguments.
if (pymeth->fMethodInfo->fFlags & CallContext::kUseHeuristics)
return PyInt_FromLong(CallContext::kUseHeuristics);

if (pymeth->fMethodInfo->fFlags & CallContext::kUseStrict)
return PyInt_FromLong(CallContext::kUseStrict);

return PyInt_FromLong(-1);
PyErr_SetString(PyExc_RuntimeError, mempolicy_error_message);
return nullptr;
}

//----------------------------------------------------------------------------
static int mp_setmempolicy(CPPOverload* pymeth, PyObject* value, void*)
static int mp_setmempolicy(CPPOverload*, PyObject*, void*)
{
// Set '_mempolicy' enum, which determines ownership of call arguments.
long mempolicy = PyLong_AsLong(value);
if (mempolicy == CallContext::kUseHeuristics) {
pymeth->fMethodInfo->fFlags |= CallContext::kUseHeuristics;
pymeth->fMethodInfo->fFlags &= ~CallContext::kUseStrict;
} else if (mempolicy == CallContext::kUseStrict) {
pymeth->fMethodInfo->fFlags |= CallContext::kUseStrict;
pymeth->fMethodInfo->fFlags &= ~CallContext::kUseHeuristics;
} else {
PyErr_SetString(PyExc_ValueError,
"expected kMemoryStrict or kMemoryHeuristics as value for __mempolicy__");
return -1;
}

return 0;
PyErr_SetString(PyExc_RuntimeError, mempolicy_error_message);
return -1;
}


Expand Down Expand Up @@ -588,7 +574,7 @@ static PyGetSetDef mp_getset[] = {
{(char*)"__creates__", (getter)mp_getcreates, (setter)mp_setcreates,
(char*)"For ownership rules of result: if true, objects are python-owned", nullptr},
{(char*)"__mempolicy__", (getter)mp_getmempolicy, (setter)mp_setmempolicy,
(char*)"For argument ownership rules: like global, either heuristic or strict", nullptr},
(char*)"Unused", nullptr},
{(char*)"__set_lifeline__", (getter)mp_getlifeline, (setter)mp_setlifeline,
(char*)"If true, set a lifeline from the return value onto self", nullptr},
{(char*)"__release_gil__", (getter)mp_getthreaded, (setter)mp_setthreaded,
Expand Down Expand Up @@ -630,8 +616,6 @@ static PyObject* mp_call(CPPOverload* pymeth, PyObject* args, PyObject* kwds)

CallContext ctxt{};
const auto mflags = pymeth->fMethodInfo->fFlags;
const auto mempolicy = (mflags & (CallContext::kUseHeuristics | CallContext::kUseStrict));
ctxt.fFlags |= mempolicy ? mempolicy : (uint64_t)CallContext::sMemoryPolicy;
ctxt.fFlags |= (mflags & CallContext::kReleaseGIL);
ctxt.fFlags |= (mflags & CallContext::kProtected);
if (IsConstructor(pymeth->fMethodInfo->fFlags)) ctxt.fFlags |= CallContext::kIsConstructor;
Expand Down Expand Up @@ -1080,8 +1064,7 @@ void CPyCppyy::CPPOverload::Set(const std::string& name, std::vector<PyCallable*
fMethodInfo->fFlags |= (CallContext::kIsCreator | CallContext::kIsConstructor);

// special case, in heuristics mode also tag *Clone* methods as creators
if (CallContext::sMemoryPolicy == CallContext::kUseHeuristics && \
name.find("Clone") != std::string::npos)
if (CallContext::GlobalPolicyFlags() & CallContext::kUseHeuristics && name.find("Clone") != std::string::npos)
fMethodInfo->fFlags |= CallContext::kIsCreator;

#if PY_VERSION_HEX >= 0x03080000
Expand Down
65 changes: 22 additions & 43 deletions src/CPyCppyyModule.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -906,41 +906,23 @@ static PyObject* AddTypeReducer(PyObject*, PyObject* args)
Py_RETURN_NONE;
}

//----------------------------------------------------------------------------
static PyObject* SetMemoryPolicy(PyObject*, PyObject* args)
{
// Set the global memory policy, which affects object ownership when objects
// are passed as function arguments.
PyObject* policy = nullptr;
if (!PyArg_ParseTuple(args, const_cast<char*>("O!"), &PyInt_Type, &policy))
return nullptr;

long old = (long)CallContext::sMemoryPolicy;

long l = PyInt_AS_LONG(policy);
if (CallContext::SetMemoryPolicy((CallContext::ECallFlags)l)) {
return PyInt_FromLong(old);
}

PyErr_Format(PyExc_ValueError, "Unknown policy %ld", l);
return nullptr;
#define DEFINE_CALL_POLICY_TOGGLE(name, flagname) \
static PyObject* name(PyObject*, PyObject* args) \
{ \
PyObject* enabled = 0; \
if (!PyArg_ParseTuple(args, const_cast<char*>("O"), &enabled)) \
return nullptr; \
\
if (CallContext::SetGlobalPolicy(CallContext::flagname, PyObject_IsTrue(enabled))) { \
Py_RETURN_TRUE; \
} \
\
Py_RETURN_FALSE; \
}

//----------------------------------------------------------------------------
static PyObject* SetGlobalSignalPolicy(PyObject*, PyObject* args)
{
// Set the global signal policy, which determines whether a jmp address
// should be saved to return to after a C++ segfault.
PyObject* setProtected = 0;
if (!PyArg_ParseTuple(args, const_cast<char*>("O"), &setProtected))
return nullptr;

if (CallContext::SetGlobalSignalPolicy(PyObject_IsTrue(setProtected))) {
Py_RETURN_TRUE;
}

Py_RETURN_FALSE;
}
DEFINE_CALL_POLICY_TOGGLE(SetHeuristicMemoryPolicy, kUseHeuristics);
DEFINE_CALL_POLICY_TOGGLE(SetGlobalSignalPolicy, kProtected);
DEFINE_CALL_POLICY_TOGGLE(SetImplicitSmartPointerConversion, kImplicitSmartPtrConversion);

//----------------------------------------------------------------------------
static PyObject* SetOwnership(PyObject*, PyObject* args)
Expand Down Expand Up @@ -1027,10 +1009,13 @@ static PyMethodDef gCPyCppyyMethods[] = {
METH_O, (char*)"Install a type pinning."},
{(char*) "_add_type_reducer", (PyCFunction)AddTypeReducer,
METH_VARARGS, (char*)"Add a type reducer."},
{(char*) "SetMemoryPolicy", (PyCFunction)SetMemoryPolicy,
METH_VARARGS, (char*)"Determines object ownership model."},
{(char*) "SetGlobalSignalPolicy", (PyCFunction)SetGlobalSignalPolicy,
METH_VARARGS, (char*)"Trap signals in safe mode to prevent interpreter abort."},
{(char*) "SetHeuristicMemoryPolicy", (PyCFunction)SetHeuristicMemoryPolicy,
METH_VARARGS, (char*)"Set the global memory policy, which affects object ownership when objects are passed as function arguments."},
{(char *)"SetGlobalSignalPolicy", (PyCFunction)SetGlobalSignalPolicy, METH_VARARGS,
(char *)"Set the global signal policy, which determines whether a jmp address should be saved to return to after a "
"C++ segfault. In practical terms: trap signals in safe mode to prevent interpreter abort."},
{(char*) "SetImplicitSmartPointerConversion", (PyCFunction)SetImplicitSmartPointerConversion,
METH_VARARGS, (char*)"Enable or disable the implicit conversion to smart pointers in function calls (on by default)."},
{(char*) "SetOwnership", (PyCFunction)SetOwnership,
METH_VARARGS, (char*)"Modify held C++ object ownership."},
{(char*) "AddSmartPtrType", (PyCFunction)AddSmartPtrType,
Expand Down Expand Up @@ -1206,12 +1191,6 @@ extern "C" void initlibcppyy()
gAbrtException = PyErr_NewException((char*)"cppyy.ll.AbortSignal", cppfatal, nullptr);
PyModule_AddObject(gThisModule, (char*)"AbortSignal", gAbrtException);

// policy labels
PyModule_AddObject(gThisModule, (char*)"kMemoryHeuristics",
PyInt_FromLong((int)CallContext::kUseHeuristics));
PyModule_AddObject(gThisModule, (char*)"kMemoryStrict",
PyInt_FromLong((int)CallContext::kUseStrict));

// gbl namespace is injected in cppyy.py

// create the memory regulator
Expand Down
40 changes: 13 additions & 27 deletions src/CallContext.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@
#include "CPyCppyy.h"
#include "CallContext.h"


//- data _____________________________________________________________________
namespace CPyCppyy {

CallContext::ECallFlags CallContext::sMemoryPolicy = CallContext::kUseStrict;
// this is just a data holder for linking; actual value is set in CPyCppyyModule.cxx
CallContext::ECallFlags CallContext::sSignalPolicy = CallContext::kNone;

} // namespace CPyCppyy
//-----------------------------------------------------------------------------
uint32_t &CPyCppyy::CallContext::GlobalPolicyFlags()
{
static uint32_t flags = 0;
return flags;
}

//-----------------------------------------------------------------------------
void CPyCppyy::CallContext::AddTemporary(PyObject* pyobj) {
Expand Down Expand Up @@ -38,24 +35,13 @@ void CPyCppyy::CallContext::Cleanup() {
}

//-----------------------------------------------------------------------------
bool CPyCppyy::CallContext::SetMemoryPolicy(ECallFlags e)
bool CPyCppyy::CallContext::SetGlobalPolicy(ECallFlags toggleFlag, bool enabled)
{
// Set the global memory policy, which affects object ownership when objects
// are passed as function arguments.
if (kUseHeuristics == e || e == kUseStrict) {
sMemoryPolicy = e;
return true;
}
return false;
}

//-----------------------------------------------------------------------------
bool CPyCppyy::CallContext::SetGlobalSignalPolicy(bool setProtected)
{
// Set the global signal policy, which determines whether a jmp address
// should be saved to return to after a C++ segfault.
bool old = sSignalPolicy == kProtected;
sSignalPolicy = setProtected ? kProtected : kNone;
auto &flags = GlobalPolicyFlags();
bool old = flags & toggleFlag;
if (enabled)
flags |= toggleFlag;
else
flags &= ~toggleFlag;
return old;
}

58 changes: 25 additions & 33 deletions src/CallContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,38 +53,34 @@ struct CallContext {
~CallContext() { if (fTemps) Cleanup(); delete fArgsVec; }

enum ECallFlags {
kNone = 0x000000,
kIsSorted = 0x000001, // if method overload priority determined
kIsCreator = 0x000002, // if method creates python-owned objects
kIsConstructor = 0x000004, // if method is a C++ constructor
kHaveImplicit = 0x000008, // indicate that implicit converters are available
kAllowImplicit = 0x000010, // indicate that implicit conversions are allowed
kNoImplicit = 0x000020, // disable implicit to prevent recursion
kCallDirect = 0x000040, // call wrapped method directly, no inheritance
kFromDescr = 0x000080, // initiated from a descriptor
kUseHeuristics = 0x000100, // if method applies heuristics memory policy
kUseStrict = 0x000200, // if method applies strict memory policy
kReleaseGIL = 0x000400, // if method should release the GIL
kSetLifeLine = 0x000800, // if return value is part of 'this'
kNeverLifeLine = 0x001000, // if the return value is never part of 'this'
kPyException = 0x002000, // Python exception during method execution
kCppException = 0x004000, // C++ exception during method execution
kProtected = 0x008000, // if method should return on signals
kUseFFI = 0x010000, // not implemented
kIsPseudoFunc = 0x020000, // internal, used for introspection
kNone = 0x000000,
kIsSorted = 0x000001, // if method overload priority determined
kIsCreator = 0x000002, // if method creates python-owned objects
kIsConstructor = 0x000004, // if method is a C++ constructor
kHaveImplicit = 0x000008, // indicate that implicit converters are available
kAllowImplicit = 0x000010, // indicate that implicit conversions are allowed
kNoImplicit = 0x000020, // disable implicit to prevent recursion
kCallDirect = 0x000040, // call wrapped method directly, no inheritance
kFromDescr = 0x000080, // initiated from a descriptor
kUseHeuristics = 0x000100, // if method applies heuristics memory policy
kImplicitSmartPtrConversion = 0x000200, // enable implicit conversion to smart pointers
kReleaseGIL = 0x000400, // if method should release the GIL
kSetLifeLine = 0x000800, // if return value is part of 'this'
kNeverLifeLine = 0x001000, // if the return value is never part of 'this'
kPyException = 0x002000, // Python exception during method execution
kCppException = 0x004000, // C++ exception during method execution
kProtected = 0x008000, // if method should return on signals
kUseFFI = 0x010000, // not implemented
kIsPseudoFunc = 0x020000, // internal, used for introspection
};

// memory handling
static ECallFlags sMemoryPolicy;
static bool SetMemoryPolicy(ECallFlags e);
static bool SetGlobalPolicy(ECallFlags e, bool enabled);

static uint32_t& GlobalPolicyFlags();

void AddTemporary(PyObject* pyobj);
void Cleanup();

// signal safety
static ECallFlags sSignalPolicy;
static bool SetGlobalSignalPolicy(bool setProtected);

Parameter* GetArgs(size_t sz) {
if (sz != (size_t)-1) fNArgs = sz;
if (fNArgs <= SMALL_ARGS_N) return fArgs;
Expand Down Expand Up @@ -145,13 +141,9 @@ inline bool ReleasesGIL(CallContext* ctxt) {
return ctxt ? (ctxt->fFlags & CallContext::kReleaseGIL) : false;
}

inline bool UseStrictOwnership(CallContext* ctxt) {
if (ctxt && (ctxt->fFlags & CallContext::kUseStrict))
return true;
if (ctxt && (ctxt->fFlags & CallContext::kUseHeuristics))
return false;

return CallContext::sMemoryPolicy == CallContext::kUseStrict;
inline bool UseStrictOwnership() {
using CC = CPyCppyy::CallContext;
return !(CC::GlobalPolicyFlags() & CC::kUseHeuristics);
}

template<CallContext::ECallFlags F>
Expand Down
19 changes: 10 additions & 9 deletions src/Converters.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -1523,13 +1523,13 @@ bool CPyCppyy::VoidArrayConverter::GetAddressSpecialCase(PyObject* pyobject, voi

//----------------------------------------------------------------------------
bool CPyCppyy::VoidArrayConverter::SetArg(
PyObject* pyobject, Parameter& para, CallContext* ctxt)
PyObject* pyobject, Parameter& para, CallContext* /*ctxt*/)
{
// just convert pointer if it is a C++ object
CPPInstance* pyobj = GetCppInstance(pyobject);
if (pyobj) {
// depending on memory policy, some objects are no longer owned when passed to C++
if (!fKeepControl && !UseStrictOwnership(ctxt))
if (!fKeepControl && !UseStrictOwnership())
pyobj->CppOwns();

// set pointer (may be null) and declare success
Expand Down Expand Up @@ -1594,7 +1594,7 @@ bool CPyCppyy::VoidArrayConverter::ToMemory(PyObject* value, void* address, PyOb
CPPInstance* pyobj = GetCppInstance(value);
if (pyobj) {
// depending on memory policy, some objects are no longer owned when passed to C++
if (!fKeepControl && CallContext::sMemoryPolicy != CallContext::kUseStrict)
if (!fKeepControl && !UseStrictOwnership())
pyobj->CppOwns();

// set pointer (may be null) and declare success
Expand Down Expand Up @@ -2114,7 +2114,7 @@ bool CPyCppyy::InstancePtrConverter<ISCONST>::SetArg(
Cppyy::TCppType_t oisa = pyobj->ObjectIsA();
if (oisa && (oisa == fClass || Cppyy::IsSubtype(oisa, fClass))) {
// depending on memory policy, some objects need releasing when passed into functions
if (!KeepControl() && !UseStrictOwnership(ctxt))
if (!KeepControl() && !UseStrictOwnership())
pyobj->CppOwns();

// calculate offset between formal and actual arguments
Expand Down Expand Up @@ -2161,7 +2161,7 @@ bool CPyCppyy::InstancePtrConverter<ISCONST>::ToMemory(PyObject* value, void* ad

if (Cppyy::IsSubtype(pyobj->ObjectIsA(), fClass)) {
// depending on memory policy, some objects need releasing when passed into functions
if (!KeepControl() && CallContext::sMemoryPolicy != CallContext::kUseStrict)
if (!KeepControl() && !UseStrictOwnership())
((CPPInstance*)value)->CppOwns();

*(void**)address = pyobj->GetObject();
Expand Down Expand Up @@ -2342,7 +2342,7 @@ bool CPyCppyy::InstancePtrPtrConverter<ISREFERENCE>::SetArg(

if (Cppyy::IsSubtype(pyobj->ObjectIsA(), fClass)) {
// depending on memory policy, some objects need releasing when passed into functions
if (!KeepControl() && !UseStrictOwnership(ctxt))
if (!KeepControl() && !UseStrictOwnership())
pyobj->CppOwns();

// set pointer (may be null) and declare success
Expand Down Expand Up @@ -2383,7 +2383,7 @@ bool CPyCppyy::InstancePtrPtrConverter<ISREFERENCE>::ToMemory(

if (Cppyy::IsSubtype(pyobj->ObjectIsA(), fClass)) {
// depending on memory policy, some objects need releasing when passed into functions
if (!KeepControl() && CallContext::sMemoryPolicy != CallContext::kUseStrict)
if (!KeepControl() && !UseStrictOwnership())
pyobj->CppOwns();

// register the value for potential recycling
Expand Down Expand Up @@ -2868,7 +2868,7 @@ bool CPyCppyy::SmartPtrConverter::SetArg(
if (Cppyy::TCppType_t tsmart = pyobj->GetSmartIsA()) {
if (Cppyy::IsSubtype(tsmart, fSmartPtrType)) {
// depending on memory policy, some objects need releasing when passed into functions
if (!fKeepControl && !UseStrictOwnership(ctxt))
if (!fKeepControl && !UseStrictOwnership())
((CPPInstance*)pyobject)->CppOwns();

// calculate offset between formal and actual arguments
Expand Down Expand Up @@ -2899,7 +2899,8 @@ bool CPyCppyy::SmartPtrConverter::SetArg(
}

// for the case where we have an ordinary object to convert
if (!pyobj->IsSmart() && Cppyy::IsSubtype(oisa, fUnderlyingType)) {
const bool implicitSmartConversion = ctxt->fFlags & CallContext::kImplicitSmartPtrConversion;
if (implicitSmartConversion && !pyobj->IsSmart() && Cppyy::IsSubtype(oisa, fUnderlyingType)) {
// create the relevant smart pointer and make the pyobject "smart"
CPPInstance* pysmart = (CPPInstance*)ConvertImplicit(fSmartPtrType, pyobject, para, ctxt, false);
if (!CPPInstance_Check(pysmart)) {
Expand Down