diff --git a/CMakeLists.txt b/CMakeLists.txt index 465d158..96d4bc9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,8 +11,8 @@ include(GNUInstallDirs) # This option won't make a lot of sense since we only ship the shared library in site-packages # Perhaps this should permanently be OFF and users can build their own CppInterOp if they want to run the tests? option(CPPJIT_ENABLE_CPPINTEROP_TESTS "enable CppInterOp tests" OFF) -set(CPPINTEROP_GIT_REPOSITORY "https://github.com/compiler-research/CppInterOp.git" CACHE STRING "") -set(CPPINTEROP_GIT_TAG "9802d61921ad5688ae42e4e628d754fc1192244d" CACHE STRING "") +set(CPPINTEROP_GIT_REPOSITORY "https://github.com/keremsahn/CppInterOp.git" CACHE STRING "") +set(CPPINTEROP_GIT_TAG "attr-design" CACHE STRING "") set(CPPINTEROP_SOURCE_DIR "" CACHE PATH "Override default CppInterOp built by ExternalProject_Add, with a path to local CppInterOp source") diff --git a/python/cppjit/__init__.py b/python/cppjit/__init__.py index 6280d6e..b024415 100644 --- a/python/cppjit/__init__.py +++ b/python/cppjit/__init__.py @@ -48,6 +48,7 @@ "add_library_path", # add a path to search for libraries "add_autoload_map", # explicitly include an autoload map "set_debug", # enable/disable debug output + "use_alloc_analyzer", # enable/disable memory ownership analyzer ] import ctypes @@ -397,6 +398,11 @@ def set_debug(enable=True): gbl.Cpp.EnableDebugOutput(enable) +def use_alloc_analyzer(enable=True): + """Enable/disable memory ownership analyzer""" + _backend.UseAllocAnalyzer(enable) + + def _get_name(tt): if isinstance(tt, str): return tt diff --git a/src/cpyrt/CPPInstance.cxx b/src/cpyrt/CPPInstance.cxx index 59cde8a..a6784da 100644 --- a/src/cpyrt/CPPInstance.cxx +++ b/src/cpyrt/CPPInstance.cxx @@ -16,6 +16,7 @@ using namespace cppjit; // Standard #include +#include #include //- data _____________________________________________________________________ @@ -226,7 +227,17 @@ void cpyrt::op_dealloc_nofree(CPPInstance* pyobj) { if (pyobj->fFlags & CPPInstance::kIsValue) { interop::CallDestructor(klass, cppobj); interop::Deallocate(klass, cppobj); - } else + } else if (pyobj->fFlags & CPPInstance::kIsMalloc) + std::free(cppobj); + else if (pyobj->fFlags & CPPInstance::kIsNoConstruct) { + if (pyobj->fFlags & CPPInstance::kIsArrayAlloc) + ::operator delete[](cppobj); + else + ::operator delete(cppobj); + } else if (pyobj->fFlags & CPPInstance::kIsArrayAlloc) + interop::Destruct(klass, cppobj, 1); + // Default case: just kIsOwner set in all of memory-ownership flags + else interop::Destruct(klass, cppobj); } cppobj = nullptr; @@ -954,10 +965,30 @@ static int op_setownership(CPPInstance* pyobj, PyObject* value, void*) { return 0; } +// Added for testing purposes +//----------------------------------------------------------------------------- +static PyObject* op_get_array_alloc(CPPInstance* pyobj, void*) { + return PyBool_FromLong((long)(pyobj->fFlags & CPPInstance::kIsArrayAlloc)); +} +//----------------------------------------------------------------------------- +static PyObject* op_get_no_construct(CPPInstance* pyobj, void*) { + return PyBool_FromLong((long)(pyobj->fFlags & CPPInstance::kIsNoConstruct)); +} +//----------------------------------------------------------------------------- +static PyObject* op_get_malloc(CPPInstance* pyobj, void*) { + return PyBool_FromLong((long)(pyobj->fFlags & CPPInstance::kIsMalloc)); +} //----------------------------------------------------------------------------- static PyGetSetDef op_getset[] = { {(char*)"__python_owns__", (getter)op_getownership, (setter)op_setownership, (char*)"If true, python manages the life time of this object", nullptr}, + {(char*)"__is_array_alloc__", (getter)op_get_array_alloc, nullptr, + (char*)"If true, the object was allocated with new[]/operator new[]", + nullptr}, + {(char*)"__is_no_construct__", (getter)op_get_no_construct, nullptr, + (char*)"If true, the memory is raw: no constructor was run", nullptr}, + {(char*)"__is_malloc__", (getter)op_get_malloc, nullptr, + (char*)"If true, the object was allocated with malloc", nullptr}, {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}}; //= cpyrt type number stubs to allow dynamic overrides ===================== diff --git a/src/cpyrt/CPPInstance.h b/src/cpyrt/CPPInstance.h index 27febdc..4a2de1b 100644 --- a/src/cpyrt/CPPInstance.h +++ b/src/cpyrt/CPPInstance.h @@ -27,21 +27,25 @@ typedef std::vector> CI_DatamemberCache_t; class CPPInstance { public: enum EFlags { - kDefault = 0x0000, - kNoWrapConv = 0x0001, // use type as-is (eg. no smart ptr wrap) - kIsOwner = 0x0002, // Python instance owns C++ object/memory - kIsExtended = 0x0004, // has extended data - kIsValue = 0x0008, // was created from a by-value return - kIsReference = 0x0010, // represents one indirection - kIsArray = 0x0020, // represents an array of objects - kIsSmartPtr = 0x0040, // is or embeds a smart pointer - kIsPtrPtr = 0x0080, // represents two indirections - kIsRValue = 0x0100, // can be used as an r-value - kIsLValue = 0x0200, // can be used as an l-value - kNoMemReg = 0x0400, // do not register with memory regulator - kIsRegulated = 0x0800, // is registered with memory regulator - kIsActual = 0x1000, // has been downcasted to actual type - kHasLifeLine = 0x2000, // has a life line set + kDefault = 0x00000, + kNoWrapConv = 0x00001, // use type as-is (eg. no smart ptr wrap) + kIsOwner = 0x00002, // Python instance owns C++ object/memory + kIsExtended = 0x00004, // has extended data + kIsValue = 0x00008, // was created from a by-value return + kIsReference = 0x00010, // represents one indirection + kIsArray = 0x00020, // represents an array of objects + kIsSmartPtr = 0x00040, // is or embeds a smart pointer + kIsPtrPtr = 0x00080, // represents two indirections + kIsRValue = 0x00100, // can be used as an r-value + kIsLValue = 0x00200, // can be used as an l-value + kNoMemReg = 0x00400, // do not register with memory regulator + kIsRegulated = 0x00800, // is registered with memory regulator + kIsActual = 0x01000, // has been downcasted to actual type + kHasLifeLine = 0x02000, // has a life line set + kIsArrayAlloc = 0x04000, // represents a heap allocated array of objects + kIsNoConstruct = + 0x08000, // represents constructor is not called in the allocation + kIsMalloc = 0x10000, // is allocated with malloc }; public: // public, as the python C-API works with C structs diff --git a/src/cpyrt/CPPMethod.cxx b/src/cpyrt/CPPMethod.cxx index 3fe3e37..1d52adf 100644 --- a/src/cpyrt/CPPMethod.cxx +++ b/src/cpyrt/CPPMethod.cxx @@ -34,6 +34,7 @@ extern PyObject* gBusException; extern PyObject* gSegvException; extern PyObject* gIllException; extern PyObject* gAbrtException; +extern bool gUseAllocAnalyzer; } // namespace cppjit::cpyrt //- public helper ------------------------------------------------------------ @@ -752,6 +753,19 @@ PyObject* cpyrt::CPPMethod::GetArgDefault(int iarg, bool silent) { bool cpyrt::CPPMethod::IsConst() { return interop::IsConstMethod(GetMethod()); } +//---------------------------------------------------------------------------- +interop::AllocType cpyrt::CPPMethod::GetAllocBehaviour() { + if (fAllocType.has_value()) + return *fAllocType; + interop::AllocType attrResult = interop::IsAllocator(GetMethod()); + if (attrResult == interop::AllocType::Unknown && gUseAllocAnalyzer) { + interop::AllocType analyzeResult = interop::GetAllocType(GetMethod()); + fAllocType = analyzeResult; + return analyzeResult; + } + fAllocType = attrResult; + return attrResult; +} //---------------------------------------------------------------------------- PyObject* cpyrt::CPPMethod::GetScopeProxy() { // Get or build the scope of this method. diff --git a/src/cpyrt/CPPMethod.h b/src/cpyrt/CPPMethod.h index 54429a2..09ed617 100644 --- a/src/cpyrt/CPPMethod.h +++ b/src/cpyrt/CPPMethod.h @@ -5,6 +5,7 @@ #include "PyCallable.h" // Standard +#include #include #include #include @@ -62,6 +63,7 @@ class CPPMethod : public PyCallable { PyObject* GetCoVarNames() override; PyObject* GetArgDefault(int iarg, bool silent = true) override; bool IsConst() override; + cppjit::interop::AllocType GetAllocBehaviour() override; PyObject* GetScopeProxy() override; interop::TCppFuncAddr_t GetFunctionAddress() override; @@ -116,6 +118,7 @@ class CPPMethod : public PyCallable { protected: // cached value that doubles as initialized flag (uninitialized if -1) int fArgsRequired; + std::optional fAllocType; }; } // namespace cppjit::cpyrt diff --git a/src/cpyrt/CPPOverload.cxx b/src/cpyrt/CPPOverload.cxx index 49778df..0fedf05 100644 --- a/src/cpyrt/CPPOverload.cxx +++ b/src/cpyrt/CPPOverload.cxx @@ -155,6 +155,12 @@ static inline PyObject* HandleReturn(CPPOverload* pymeth, CPPInstance* im_self, CPPInstance* cppres = (CPPInstance*)(CPPInstance_Check(result) ? result : nullptr); + interop::AllocType AT = + pymeth->fMethodInfo->fMethods[0]->GetAllocBehaviour(); + if (AT != interop::AllocType::None && AT != interop::AllocType::Null && + AT != interop::AllocType::Unknown) + pymeth->fMethodInfo->fFlags |= CallContext::kIsCreator; + // if this method creates new objects, always take ownership if (IsCreator(pymeth->fMethodInfo->fFlags)) { @@ -165,8 +171,28 @@ static inline PyObject* HandleReturn(CPPOverload* pymeth, CPPInstance* im_self, } // ... or be a regular method with an object proxy return value - else if (cppres) + else if (cppres) { cppres->PythonOwns(); + // After giving ownership, set proper flags to indicate allocation + // method/func + switch (AT) { + case interop::AllocType::Malloc: + cppres->fFlags |= CPPInstance::kIsMalloc; + break; + case interop::AllocType::NewArr: + cppres->fFlags |= CPPInstance::kIsArrayAlloc; + break; + case interop::AllocType::OperatorNew: + cppres->fFlags |= CPPInstance::kIsNoConstruct; + break; + case interop::AllocType::OperatorNewArr: + cppres->fFlags |= CPPInstance::kIsArrayAlloc; + cppres->fFlags |= CPPInstance::kIsNoConstruct; + break; + default: + break; + } + } } // if this new object falls inside self, make sure its lifetime is proper diff --git a/src/cpyrt/PyCallable.h b/src/cpyrt/PyCallable.h index 4b79ab0..e1238ab 100644 --- a/src/cpyrt/PyCallable.h +++ b/src/cpyrt/PyCallable.h @@ -37,6 +37,9 @@ class PyCallable { virtual PyObject* GetCoVarNames() = 0; virtual PyObject* GetArgDefault(int /* iarg */, bool silent = true) = 0; virtual bool IsConst() { return false; } + virtual cppjit::interop::AllocType GetAllocBehaviour() { + return cppjit::interop::AllocType::None; + } virtual PyObject* GetScopeProxy() = 0; virtual interop::TCppFuncAddr_t GetFunctionAddress() = 0; diff --git a/src/cpyrt/cpyrtModule.cxx b/src/cpyrt/cpyrtModule.cxx index b2c0733..4b16ffe 100644 --- a/src/cpyrt/cpyrtModule.cxx +++ b/src/cpyrt/cpyrtModule.cxx @@ -279,6 +279,7 @@ PyObject* gAbrtException = nullptr; std::unordered_set gPinnedTypes; std::ostringstream gCapturedError; std::streambuf* gOldErrorBuffer = nullptr; +bool gUseAllocAnalyzer = false; std::unordered_map>& pythonizations() { static std::unordered_map> pyzMap; @@ -1012,6 +1013,20 @@ static PyObject* EndCaptureStderr(PyObject*, PyObject*) { return Py_BuildValue("s", capturedError.c_str()); } + +//---------------------------------------------------------------------------- +static PyObject* UseAllocAnalyzer(PyObject*, PyObject* args) { + // Set allocation-analyzer policy, disabled by default + // Usage: enabling ->SetUseAllocAnalyzer(True) / SetUseAllocAnalyzer(1) + // disabling ->SetUseAllocAnalyzer(False) / SetUseAllocAnalyzer(0) + int enable = 0; + if (!PyArg_ParseTuple(args, const_cast("p"), &enable)) + return nullptr; + + gUseAllocAnalyzer = enable; + + Py_RETURN_NONE; +} } // unnamed namespace //- data ----------------------------------------------------------------------- @@ -1061,6 +1076,8 @@ static PyMethodDef gcpyrtMethods[] = { METH_NOARGS, (char*)"Begin capturing stderr to a in memory buffer."}, {(char*)"_end_capture_stderr", (PyCFunction)EndCaptureStderr, METH_NOARGS, (char*)"End capturing stderr and returns the captured buffer."}, + {(char*)"UseAllocAnalyzer", (PyCFunction)UseAllocAnalyzer, METH_VARARGS, + (char*)"Enable/disable memory-allocation analyzer."}, {nullptr, nullptr, 0, nullptr}}; struct module_state { diff --git a/src/interop/cppjit_interop.h b/src/interop/cppjit_interop.h index ca7c07e..6f996d0 100644 --- a/src/interop/cppjit_interop.h +++ b/src/interop/cppjit_interop.h @@ -44,6 +44,7 @@ typedef Cpp::FuncRef TCppMethod_t; typedef Cpp::InterpRef TInterp_t; typedef size_t TCppIndex_t; typedef void* TCppFuncAddr_t; +typedef Cpp::AllocType AllocType; // direct interpreter access ------------------------------------------------- RPY_EXPORTED @@ -132,7 +133,7 @@ void Deallocate(TCppScope_t scope, TCppObject_t instance); RPY_EXPORTED TCppObject_t Construct(TCppScope_t scope, void* arena = nullptr); RPY_EXPORTED -void Destruct(TCppScope_t scope, TCppObject_t instance); +void Destruct(TCppScope_t scope, TCppObject_t instance, size_t count = 0); // method/function dispatching ----------------------------------------------- RPY_EXPORTED @@ -297,6 +298,10 @@ RPY_EXPORTED std::string GetDoxygenComment(TCppScope_t scope, bool strip_markers = true); RPY_EXPORTED bool IsConstMethod(TCppMethod_t); +RPY_EXPORTED +AllocType IsAllocator(TCppMethod_t); +RPY_EXPORTED +AllocType GetAllocType(TCppMethod_t); // Templated method/function reflection information // ------------------------------------ RPY_EXPORTED diff --git a/src/interop/interop_wrapper.cxx b/src/interop/interop_wrapper.cxx index 1b5b0eb..b494304 100644 --- a/src/interop/interop_wrapper.cxx +++ b/src/interop/interop_wrapper.cxx @@ -817,10 +817,11 @@ interop::TCppObject_t interop::Construct(TCppScope_t scope, return Cpp::Construct(scope, arena, /*count=*/1); } -void interop::Destruct(TCppScope_t scope, TCppObject_t instance) { +void interop::Destruct(TCppScope_t scope, TCppObject_t instance, + size_t count /*=0*/) { std::lock_guard Lock( InterOpMutex); // TODO: this shouldn't locks the JIT call - Cpp::Destruct(instance, scope, true, /*count=*/0); + Cpp::Destruct(instance, scope, true, count); } static inline bool copy_args(Parameter* args, size_t nargs, void** vargs) { @@ -1200,6 +1201,16 @@ interop::TCppType_t interop::GetMethodReturnType(TCppMethod_t method) { return Cpp::GetFunctionReturnType(method); } +interop::AllocType interop::IsAllocator(TCppMethod_t method) { + std::lock_guard Lock(InterOpMutex); + return Cpp::IsAllocator(method); +} + +interop::AllocType interop::GetAllocType(TCppMethod_t method) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetAllocType(method); +} + std::string interop::GetMethodReturnTypeAsString(TCppMethod_t method) { std::lock_guard Lock(InterOpMutex); return Cpp::GetTypeAsString( diff --git a/test/Makefile b/test/Makefile index 7f1433e..a9d79c3 100644 --- a/test/Makefile +++ b/test/Makefile @@ -10,6 +10,7 @@ dictnames = advancedcpp \ doc_helper \ example01 \ fragile \ + memory_analysis \ operators \ overloads \ pythonizables \ diff --git a/test/cpp/MemoryOwnership/MemOwnrship.apinotes b/test/cpp/MemoryOwnership/MemOwnrship.apinotes new file mode 100644 index 0000000..397d4f9 --- /dev/null +++ b/test/cpp/MemoryOwnership/MemOwnrship.apinotes @@ -0,0 +1,9 @@ +Name: MemOwnrship +Functions: + - Name: memOwnOperatorNew + SwiftReturnOwnership: cppAllocOperatorNew +Tags: + - Name: memOwn + Methods: + - Name: memOwnAllocator + SwiftReturnOwnership: cppAllocNew diff --git a/test/cpp/MemoryOwnership/memory_analysis_redecl.h b/test/cpp/MemoryOwnership/memory_analysis_redecl.h new file mode 100644 index 0000000..d8e9e63 --- /dev/null +++ b/test/cpp/MemoryOwnership/memory_analysis_redecl.h @@ -0,0 +1,10 @@ +#ifndef MEMORY_ANALYSIS_REDECL_H +#define MEMORY_ANALYSIS_REDECL_H +#include "../memory_analysis.h" + +namespace memory { +[[clang::annotate("cppAllocNew")]] +memOwn* allocDefaultMemOwn(); +} + +#endif diff --git a/test/cpp/MemoryOwnership/module.modulemap b/test/cpp/MemoryOwnership/module.modulemap new file mode 100644 index 0000000..123a620 --- /dev/null +++ b/test/cpp/MemoryOwnership/module.modulemap @@ -0,0 +1 @@ +module MemOwnrship { header "../memory_analysis.h" } diff --git a/test/cpp/memory_analysis.cxx b/test/cpp/memory_analysis.cxx new file mode 100644 index 0000000..f0a5759 --- /dev/null +++ b/test/cpp/memory_analysis.cxx @@ -0,0 +1,45 @@ +#include "memory_analysis.h" + +namespace memory { + +int memOwn::dtorCount = 0; + +memOwn::memOwn(int value) : val(value) {} + +memOwn::memOwn() { val = 0; } + +memOwn::~memOwn() { ++dtorCount; } + +__attribute__((malloc)) memAnalysisKlass* mallocAttr() { + return (memAnalysisKlass*)malloc(sizeof(memAnalysisKlass)); +} + +__attribute__((ownership_returns(malloc))) memAnalysisKlass* +ownershipReturnsAttr() { + return (memAnalysisKlass*)malloc(sizeof(memAnalysisKlass)); +} + +// Expected to not return ownership when analysis is off, and there is just +// attr-check +memAnalysisKlass* noAttr() { return new memAnalysisKlass; } + +memOwn* memOwnOperatorNew() { return (memOwn*)::operator new(sizeof(memOwn)); } + +memOwn* allocDefaultMemOwn() { return new memOwn; } + +memOwn* noAttrAlloc() { return new memOwn; } + +memOwn* allocOperatorNewArrAttr(size_t size) { + return (memOwn*)::operator new[](sizeof(memOwn) * size); +} + +memOwn* allocNewArrAttr(int count) { return new memOwn[count]; } + +memOwn* allocMallocAttr(size_t size) { + return (memOwn*)malloc(sizeof(memOwn) * size); +} + +memOwn* allocOperatorNewAttr() { + return (memOwn*)::operator new(sizeof(memOwn)); +} +} // namespace memory diff --git a/test/cpp/memory_analysis.h b/test/cpp/memory_analysis.h new file mode 100644 index 0000000..a6626a6 --- /dev/null +++ b/test/cpp/memory_analysis.h @@ -0,0 +1,61 @@ +#ifndef MEMORY_ANALYSIS_H +#define MEMORY_ANALYSIS_H + +#include +#include +namespace memory { + +class memAnalysisKlass { +public: + int val; +}; +__attribute__((malloc)) memAnalysisKlass* mallocAttr(); +__attribute__((ownership_returns(malloc))) memAnalysisKlass* +ownershipReturnsAttr(); +memAnalysisKlass* noAttr(); + +struct memOwn { + int val; + static int dtorCount; + memOwn(int value); + memOwn(); + // Attribute injected by APINotes + static memOwn* memOwnAllocator(int x) { return new memOwn(x); } + ~memOwn(); +}; + +// Attribute injected by APINotes +memOwn* memOwnOperatorNew(); + +// Attribute injected by redeclaration +memOwn* allocDefaultMemOwn(); + +// No ownership attribute anywhere +memOwn* noAttrAlloc(); + +inline memAnalysisKlass* allocAnalyzerOn() { return new memAnalysisKlass; } +inline memAnalysisKlass* allocAnalyzerOff() { return new memAnalysisKlass; } +inline memOwn* allocOperatorNewArr(size_t size) { + return (memOwn*)::operator new[](sizeof(memOwn) * size); +} +inline memOwn* allocNewArr(int count) { return new memOwn[count]; } +inline memOwn* allocMalloc(size_t size) { + return (memOwn*)malloc(sizeof(memOwn) * size); +} +inline memOwn* allocOperatorNew() { + return (memOwn*)::operator new(sizeof(memOwn)); +} +[[clang::annotate("cppAllocOperatorNewArr")]] +memOwn* allocOperatorNewArrAttr(size_t size); + +[[clang::annotate("cppAllocNewArr")]] +memOwn* allocNewArrAttr(int count); + +[[clang::annotate("cppAllocMalloc")]] +memOwn* allocMallocAttr(size_t size); + +[[clang::annotate("cppAllocOperatorNew")]] +memOwn* allocOperatorNewAttr(); +} // namespace memory + +#endif // MEMORY_ANALYSIS_H diff --git a/test/support.py b/test/support.py index de2532d..922db67 100644 --- a/test/support.py +++ b/test/support.py @@ -115,4 +115,12 @@ def setup_make(targetname): #endif\n""") == 1 ) +IS_CLANG_LT_22 = ( + cppjit.evaluate("""#if __clang_major__ < 22 + true + #else + false + #endif\n""") + == 1 +) IS_VALGRIND = True if os.getenv("IS_VALGRIND") else False diff --git a/test/test_memoryanalysis.py b/test/test_memoryanalysis.py new file mode 100644 index 0000000..251c111 --- /dev/null +++ b/test/test_memoryanalysis.py @@ -0,0 +1,281 @@ +import os +import subprocess +import sys + +import py +from pytest import mark +from support import IS_CLANG_LT_22, IS_CLING, setup_make + +currpath = py.path.local(__file__).dirpath() +test_dct = str(currpath.join("cpp/memory_analysisDict")) + +FLAGS = "-fmodules -fimplicit-module-maps -fapinotes-modules" +IN_CHILD = "-fapinotes-modules" in os.getenv("CPPINTEROP_EXTRA_INTERPRETER_ARGS", "") + +skip_if_inline_from_module = mark.skipif( + IN_CHILD and IS_CLANG_LT_22, + reason="LLVM < 22 does not emit inline definitions that come from a module", +) + + +def setup_module(mod): + setup_make("memory_analysis") + + +@mark.skipif( + IN_CHILD or IS_CLING, + reason="Cling asserts in collectModuleMaps when built with " + FLAGS, +) +def test00_driver(): + env = os.environ.copy() + env["CPPINTEROP_EXTRA_INTERPRETER_ARGS"] = ( + env.get("CPPINTEROP_EXTRA_INTERPRETER_ARGS", "") + " " + FLAGS + ) + subprocess.check_call([sys.executable, "-m", "pytest", __file__], env=env) + + +class TestMEMORYANALYSIS: + def setup_class(cls): + cls.test_dct = test_dct + import cppjit + + cppjit.add_include_path(str(currpath.join("cpp", "MemoryOwnership"))) + cppjit.include("../memory_analysis.h") + cppjit.include("memory_analysis_redecl.h") + cls.memory_analysis = cppjit.load_library(cls.test_dct + ".so") + + def test01_malloc_attr(self): + import cppjit + + obj = cppjit.gbl.memory.mallocAttr() + assert type(obj) == cppjit.gbl.memory.memAnalysisKlass + assert obj.__python_owns__ + assert obj.__is_malloc__ + + def test02_ownership_returns_attr(self): + import cppjit + + obj = cppjit.gbl.memory.ownershipReturnsAttr() + assert type(obj) == cppjit.gbl.memory.memAnalysisKlass + assert obj.__python_owns__ + assert obj.__is_malloc__ + + def test03_no_attr(self): + import cppjit + + obj = cppjit.gbl.memory.noAttr() + assert type(obj) == cppjit.gbl.memory.memAnalysisKlass + assert not obj.__python_owns__ + obj.__python_owns__ = True + + def test04_redecl_attr(self): + import cppjit + + cppjit.gbl.memory.memOwn.dtorCount = 0 + + def alloc(): + obj = cppjit.gbl.memory.allocDefaultMemOwn() + assert type(obj) == cppjit.gbl.memory.memOwn + assert obj.__python_owns__ + assert not obj.__is_malloc__ + assert not obj.__is_no_construct__ + assert not obj.__is_array_alloc__ + + alloc() + assert cppjit.gbl.memory.memOwn.dtorCount == 1 + + def test05_redecl_no_attr(self): + import cppjit + + obj = cppjit.gbl.memory.noAttrAlloc() + assert type(obj) == cppjit.gbl.memory.memOwn + assert not obj.__python_owns__ + obj.__python_owns__ = True + + # Setting only python_owns, intends object is allocated with new + assert not obj.__is_malloc__ + assert not obj.__is_no_construct__ + assert not obj.__is_array_alloc__ + + @skip_if_inline_from_module + def test06_analyzer_on(self): + import cppjit + + cppjit.use_alloc_analyzer(True) + obj = cppjit.gbl.memory.allocAnalyzerOn() + assert obj.__python_owns__ + + @skip_if_inline_from_module + def test07_analyzer_off(self): + import cppjit + + cppjit.use_alloc_analyzer(False) + obj = cppjit.gbl.memory.allocAnalyzerOff() + assert not (obj.__python_owns__) + obj.__python_owns__ = True + + @skip_if_inline_from_module + def test08_analyzer_off_but_cache(self): + import cppjit + + cppjit.use_alloc_analyzer(True) + obj = cppjit.gbl.memory.allocAnalyzerOn() + assert obj.__python_owns__ + + cppjit.use_alloc_analyzer(False) + obj2 = cppjit.gbl.memory.allocAnalyzerOn() + assert obj2.__python_owns__ + + @skip_if_inline_from_module + def test09_allocwith_operator_newarr(self): + import cppjit + + cppjit.use_alloc_analyzer(True) + cppjit.gbl.memory.memOwn.dtorCount = 0 + + def alloc(): + obj = cppjit.gbl.memory.allocOperatorNewArr(5) + assert obj.__python_owns__ + assert obj.__is_no_construct__ + assert obj.__is_array_alloc__ + + alloc() + assert cppjit.gbl.memory.memOwn.dtorCount == 0 + + @skip_if_inline_from_module + def test10_allocwith_newarr(self): + import cppjit + + cppjit.use_alloc_analyzer(True) + cppjit.gbl.memory.memOwn.dtorCount = 0 + + def alloc(): + obj = cppjit.gbl.memory.allocNewArr(5) + assert obj.__python_owns__ + assert not obj.__is_no_construct__ + assert obj.__is_array_alloc__ + + alloc() + assert cppjit.gbl.memory.memOwn.dtorCount == 5 + + @skip_if_inline_from_module + def test11_allocwith_malloc(self): + import cppjit + + cppjit.use_alloc_analyzer(True) + cppjit.gbl.memory.memOwn.dtorCount = 0 + + def alloc(): + obj = cppjit.gbl.memory.allocMalloc(5) + assert obj.__python_owns__ + assert not obj.__is_no_construct__ + assert not obj.__is_array_alloc__ + assert obj.__is_malloc__ + + alloc() + assert cppjit.gbl.memory.memOwn.dtorCount == 0 + + @skip_if_inline_from_module + def test12_allocwith_operator_new(self): + import cppjit + + cppjit.use_alloc_analyzer(True) + cppjit.gbl.memory.memOwn.dtorCount = 0 + + def alloc(): + obj = cppjit.gbl.memory.allocOperatorNew() + assert obj.__python_owns__ + assert obj.__is_no_construct__ + assert not obj.__is_array_alloc__ + assert not obj.__is_malloc__ + + alloc() + assert cppjit.gbl.memory.memOwn.dtorCount == 0 + + def test13_allocwith_operator_newarr_attr(self): + import cppjit + + cppjit.use_alloc_analyzer(True) + cppjit.gbl.memory.memOwn.dtorCount = 0 + + def alloc(): + obj = cppjit.gbl.memory.allocOperatorNewArrAttr(5) + assert obj.__python_owns__ + assert obj.__is_no_construct__ + assert obj.__is_array_alloc__ + + alloc() + assert cppjit.gbl.memory.memOwn.dtorCount == 0 + + def test14_allocwith_newarr_attr(self): + import cppjit + + cppjit.use_alloc_analyzer(True) + cppjit.gbl.memory.memOwn.dtorCount = 0 + + def alloc(): + obj = cppjit.gbl.memory.allocNewArrAttr(5) + assert obj.__python_owns__ + assert not obj.__is_no_construct__ + assert obj.__is_array_alloc__ + + alloc() + assert cppjit.gbl.memory.memOwn.dtorCount == 5 + + def test15_allocwith_malloc_attr(self): + import cppjit + + cppjit.use_alloc_analyzer(True) + cppjit.gbl.memory.memOwn.dtorCount = 0 + + def alloc(): + obj = cppjit.gbl.memory.allocMallocAttr(5) + assert obj.__python_owns__ + assert not obj.__is_no_construct__ + assert not obj.__is_array_alloc__ + assert obj.__is_malloc__ + + alloc() + assert cppjit.gbl.memory.memOwn.dtorCount == 0 + + def test16_allocwith_operator_new_attr(self): + import cppjit + + cppjit.use_alloc_analyzer(True) + cppjit.gbl.memory.memOwn.dtorCount = 0 + + def alloc(): + obj = cppjit.gbl.memory.allocOperatorNewAttr() + assert obj.__python_owns__ + assert obj.__is_no_construct__ + assert not obj.__is_array_alloc__ + assert not obj.__is_malloc__ + + alloc() + assert cppjit.gbl.memory.memOwn.dtorCount == 0 + + +@mark.skipif(not IN_CHILD, reason="needs " + FLAGS) +class TestMEMORYANALYSIS_APINOTES: + def setup_class(cls): + cls.test_dct = test_dct + import cppjit + + cppjit.add_include_path(str(currpath.join("cpp", "MemoryOwnership"))) + cppjit.include("../memory_analysis.h") + cls.memory_analysis = cppjit.load_library(cls.test_dct + ".so") + + def test01_apinotes_attr_method(self): + import cppjit + + obj = cppjit.gbl.memory.memOwn.memOwnAllocator(5) + assert type(obj) == cppjit.gbl.memory.memOwn + assert obj.__python_owns__ + + def test02_apinotes_attr_func(self): + import cppjit + + obj = cppjit.gbl.memory.memOwnOperatorNew() + assert type(obj) == cppjit.gbl.memory.memOwn + assert obj.__python_owns__ + assert obj.__is_no_construct__