diff --git a/src/cpyrt/CPPMethod.cxx b/src/cpyrt/CPPMethod.cxx index 761d0ed..3edf4a3 100644 --- a/src/cpyrt/CPPMethod.cxx +++ b/src/cpyrt/CPPMethod.cxx @@ -749,6 +749,23 @@ PyObject* cpyrt::CPPMethod::GetArgDefault(int iarg, bool silent) { bool cpyrt::CPPMethod::IsConst() { return interop::IsConstMethod(GetMethod()); } +//---------------------------------------------------------------------------- +// FIXME: For now every allocation is assumed to be done with `new` +// will be fixed soon. Also the reason function returns an AllocType +// instead of bool, IsAllocator function from CppInterOp changed a little, +// but these changes did not merged to main yet, another reason is next PR +// will add user-optional analyzer so the function needs to return allocation +// way +interop::AllocType cpyrt::CPPMethod::GetAllocBehaviour() { + if (fAllocType.has_value()) + return *fAllocType; + if (interop::IsAllocator(GetMethod())) { + fAllocType = interop::AllocType::New; + return interop::AllocType::New; + } + fAllocType = interop::AllocType::None; + return interop::AllocType::None; +} //---------------------------------------------------------------------------- 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 8bda467..a51b403 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)) { 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/cppjit_interop.h b/src/cpyrt/cppjit_interop.h index f2cdb69..9a56818 100644 --- a/src/cpyrt/cppjit_interop.h +++ b/src/cpyrt/cppjit_interop.h @@ -92,6 +92,18 @@ struct ObjectRef { friend bool operator==(ObjectRef a, ObjectRef b) { return a.data == b.data; } friend bool operator!=(ObjectRef a, ObjectRef b) { return !(a == b); } }; + +enum class AllocType : unsigned char { + None, + New, + NewArr, + Malloc, + Unknown, + CustomAlloc, + Null, + OperatorNew, + OperatorNewArr +}; } // namespace Cpp template <> struct std::hash { @@ -122,6 +134,7 @@ typedef Cpp::ObjectRef TCppObject_t; typedef Cpp::FuncRef TCppMethod_t; typedef size_t TCppIndex_t; typedef void* TCppFuncAddr_t; +typedef Cpp::AllocType AllocType; // direct interpreter access ------------------------------------------------- CPPJIT_IMPORT @@ -368,6 +381,8 @@ CPPJIT_IMPORT std::string GetDoxygenComment(TCppScope_t scope, bool strip_markers = true); CPPJIT_IMPORT bool IsConstMethod(TCppMethod_t); +CPPJIT_IMPORT +bool IsAllocator(TCppMethod_t); // Templated method/function reflection information // ------------------------------------ CPPJIT_IMPORT diff --git a/src/interop/cpp_cppjit.h b/src/interop/cpp_cppjit.h index 5fb4be4..76c7194 100644 --- a/src/interop/cpp_cppjit.h +++ b/src/interop/cpp_cppjit.h @@ -53,6 +53,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 @@ -306,6 +307,8 @@ RPY_EXPORTED std::string GetDoxygenComment(TCppScope_t scope, bool strip_markers = true); RPY_EXPORTED bool IsConstMethod(TCppMethod_t); +RPY_EXPORTED +bool IsAllocator(TCppMethod_t); // Templated method/function reflection information // ------------------------------------ RPY_EXPORTED diff --git a/src/interop/interop_wrapper.cxx b/src/interop/interop_wrapper.cxx index 0b71747..a7be04d 100644 --- a/src/interop/interop_wrapper.cxx +++ b/src/interop/interop_wrapper.cxx @@ -1196,6 +1196,11 @@ interop::TCppType_t interop::GetMethodReturnType(TCppMethod_t method) { return Cpp::GetFunctionReturnType(method); } +bool interop::IsAllocator(TCppMethod_t method) { + std::lock_guard Lock(InterOpMutex); + return Cpp::IsAllocator(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 e07e775..4c9c019 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/memory_analysis.cxx b/test/cpp/memory_analysis.cxx new file mode 100644 index 0000000..a54317e --- /dev/null +++ b/test/cpp/memory_analysis.cxx @@ -0,0 +1,15 @@ +#include "memory_analysis.h" +__attribute__((malloc)) memory::memAnalysisKlass* memory::mallocAttr() { + return new memory::memAnalysisKlass; +} + +__attribute__((ownership_returns(malloc))) memory::memAnalysisKlass* +memory::ownershipReturnsAttr() { + return new memory::memAnalysisKlass; +} + +// Expected to not return ownership when analysis is off, and there is just +// attr-check +memory::memAnalysisKlass* memory::noAttr() { + return new memory::memAnalysisKlass; +} \ No newline at end of file diff --git a/test/cpp/memory_analysis.h b/test/cpp/memory_analysis.h new file mode 100644 index 0000000..d7744c5 --- /dev/null +++ b/test/cpp/memory_analysis.h @@ -0,0 +1,19 @@ +#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(); + +} // namespace memory + +#endif // MEMORY_ANALYSIS_H \ No newline at end of file diff --git a/test/test_memoryanalysis.py b/test/test_memoryanalysis.py new file mode 100644 index 0000000..bae0c01 --- /dev/null +++ b/test/test_memoryanalysis.py @@ -0,0 +1,39 @@ +import py +from support import setup_make + +currpath = py.path.local(__file__).dirpath() +test_dct = str(currpath.join("cpp/memory_analysisDict")) + + +def setup_module(mod): + setup_make("memory_analysis") + + +class TestMEMORYANALYSIS: + def setup_class(cls): + cls.test_dct = test_dct + import cppjit + + cls.memory_analysis = cppjit.load_reflection_info(cls.test_dct) + + def test01_malloc_attr(self): + import cppjit + + obj = cppjit.gbl.memory.mallocAttr() + assert type(obj) == cppjit.gbl.memory.memAnalysisKlass + assert obj.__python_owns__ + + def test02_ownership_returns_attr(self): + import cppjit + + obj = cppjit.gbl.memory.ownershipReturnsAttr() + assert type(obj) == cppjit.gbl.memory.memAnalysisKlass + assert obj.__python_owns__ + + 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