-
-
Notifications
You must be signed in to change notification settings - Fork 386
Review/memory management #1439
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
Review/memory management #1439
Conversation
Reviewer's GuideThis PR refactors the SWIG interface to target Python 3 only, overhauling IO stream typemaps for strict str/bytes handling, extends handle typemaps with debug reference-count tracking and utility methods, and enhances exception wrappers to map OpenCASCADE errors to specific Python exception types with richer context. Sequence Diagram for Enhanced OpenCASCADE Exception HandlingsequenceDiagram
actor Developer
Developer->>PythonApp: Call OCC_Operation()
PythonApp->>SWIGWrapper: OCC_Operation()
SWIGWrapper->>OpenCASCADELib: NativeCall()
OpenCASCADELib-->>SWIGWrapper: throws Standard_Failure (e.g., Standard_OutOfRange)
SWIGWrapper->>ExceptionHandler: process_opencascade_exception(error, "$name", "$parentclassname")
ExceptionHandler->>ExceptionHandler: get_exception_type(error)
ExceptionHandler-->>SWIGWrapper: Returns PyExc_IndexError (example)
SWIGWrapper->>PythonApp: Raises IndexError("OpenCASCADE Error [Standard_OutOfRange]: Description (in Class::Method)")
PythonApp-->>Developer: Receives specific Python IndexError
Sequence Diagram for Refactored Python to C++ Stream InputsequenceDiagram
actor Developer
Developer->>PythonApp: call_occ_func_with_stream_data("some string data")
PythonApp->>SWIGTypemap_IStream: Pass Python str/bytes for std::istream&
alt Input is Python str
SWIGTypemap_IStream->>SWIGTypemap_IStream: PyUnicode_AsEncodedString(input, "UTF-8", "strict")
opt Encoding Fails
SWIGTypemap_IStream-->>PythonApp: Raise UnicodeError
end
SWIGTypemap_IStream->>SWIGTypemap_IStream: PyBytes_AsString(encoded_bytes)
else Input is Python bytes
SWIGTypemap_IStream->>SWIGTypemap_IStream: PyBytes_AsString(input)
end
opt Data Extraction Fails
SWIGTypemap_IStream-->>PythonApp: Raise ValueError
end
SWIGTypemap_IStream->>SWIGTypemap_IStream: Create std::string cpp_data(data_ptr)
SWIGTypemap_IStream->>OpenCASCADELib: Pass new std::stringstream(cpp_data)
OpenCASCADELib->>OpenCASCADELib: Process stream data
OpenCASCADELib-->>SWIGTypemap_IStream: Return
SWIGTypemap_IStream->>SWIGTypemap_IStream: delete stringstream (freearg)
SWIGTypemap_IStream-->>PythonApp: Return from function
PythonApp-->>Developer: Result
Class Diagram for OpenCASCADE Exception to Python Exception MappingclassDiagram
class Standard_Failure {
<<OpenCASCADE Error>>
+DynamicType() Name
+GetMessageString()
}
class Standard_OutOfRange
class Standard_TypeMismatch
class Standard_NullObject
class Standard_NotImplemented
class Standard_OutOfMemory
Standard_Failure <|-- Standard_OutOfRange
Standard_Failure <|-- Standard_TypeMismatch
Standard_Failure <|-- Standard_NullObject
Standard_Failure <|-- Standard_NotImplemented
Standard_Failure <|-- Standard_OutOfMemory
class PythonException {
<<Python Built-in>>
}
class IndexError
class TypeError
class ValueError
class NotImplementedError
class MemoryError
PythonException <|-- IndexError
PythonException <|-- TypeError
PythonException <|-- ValueError
PythonException <|-- NotImplementedError
PythonException <|-- MemoryError
class SWIGExceptionMapper {
<<SWIG Utility>>
+get_exception_type(occ_error: Standard_Failure) : PythonExceptionType
+process_opencascade_exception(occ_error: Standard_Failure, method: string, class: string)
}
SWIGExceptionMapper ..> Standard_OutOfRange : maps to IndexError
SWIGExceptionMapper ..> Standard_TypeMismatch : maps to TypeError
SWIGExceptionMapper ..> Standard_NullObject : maps to ValueError
SWIGExceptionMapper ..> Standard_NotImplemented : maps to NotImplementedError
SWIGExceptionMapper ..> Standard_OutOfMemory : maps to MemoryError
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @tpaviot - I've reviewed your changes - here's some feedback:
- The duplicated typemap(in) implementations for std::istream and std::stringstream could be refactored into a shared helper or macro to reduce code duplication.
- Mapping exception types via substring matching on names is brittle—consider using direct DynamicType() comparisons or a lookup table for more robust exception mapping.
- The global DEBUG_MEMORY counters are not thread-safe; if the library may be used in multi-threaded contexts consider using atomic counters or synchronization.
Here's what I looked at during the review
- 🟡 General issues: 4 issues found
- 🟢 Security: all looks good
- 🟢 Testing: all looks good
- 🟢 Documentation: all looks good
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
// Standard_IStream | ||
//============================================================================= | ||
// Input stream conversion: Python str/bytes -> std::istream& | ||
//============================================================================= | ||
%typemap(in) std::istream& { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Duplicate typemap logic for istream and stringstream
Refactor the common logic into a shared helper or macro to reduce duplication and ease future maintenance.
Suggested implementation:
// Input stream conversion: Python str/bytes -> std::istream&
//=============================================================================
%typemap(in) std::istream& {
if (!PyUnicode_Check($input) && !PyBytes_Check($input)) {
PyErr_SetString(PyExc_TypeError, "Expected str or bytes object");
SWIG_fail;
}
PyObject* encoded_bytes = nullptr;
const char* data_ptr = nullptr;
=======
%define PYTHON_STR_OR_BYTES_TO_ISTREAM(VAR_NAME)
if (!PyUnicode_Check($input) && !PyBytes_Check($input)) {
PyErr_SetString(PyExc_TypeError, "Expected str or bytes object");
SWIG_fail;
}
PyObject* encoded_bytes = nullptr;
const char* data_ptr = nullptr;
Py_ssize_t data_len = 0;
if (PyUnicode_Check($input)) {
encoded_bytes = PyUnicode_AsEncodedString($input, "UTF-8", "strict");
if (!encoded_bytes) {
SWIG_fail;
}
data_ptr = PyBytes_AsString(encoded_bytes);
data_len = PyBytes_Size(encoded_bytes);
} else {
data_ptr = PyBytes_AsString($input);
data_len = PyBytes_Size($input);
}
std::string VAR_NAME(data_ptr, data_len);
if (encoded_bytes) {
Py_DECREF(encoded_bytes);
}
%enddef
//=============================================================================
// Input stream conversion: Python str/bytes -> std::istream&
//=============================================================================
%typemap(in) std::istream& {
PYTHON_STR_OR_BYTES_TO_ISTREAM(_swig_istream_data)
$1 = *(new std::istringstream(_swig_istream_data));
}
Add a similar typemap for std::stringstream& using the macro:
//=============================================================================
// Input stream conversion: Python str/bytes -> std::stringstream&
//=============================================================================
%typemap(in) std::stringstream& {
PYTHON_STR_OR_BYTES_TO_ISTREAM(_swig_stringstream_data)
$1 = *(new std::stringstream(_swig_stringstream_data));
}
If you have other stream types, you can use the macro in the same way, just changing the variable name and stream type as needed.
// global counter for dbug (enabled using DEBUG_MEMORY flag) | ||
#ifdef DEBUG_MEMORY | ||
%inline %{ | ||
static int handle_creation_count = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (bug_risk): Static debug counters get duplicated per module
To maintain a single global count, declare these counters as 'extern' in a header and define them in one .cpp file instead of using 'static' inside the inline block.
Suggested implementation:
%{
#include "OccHandleDebugCounters.h"
%}
%inline %{
void reset_handle_counters() {
handle_creation_count = 0;
- Create a header file
OccHandleDebugCounters.h
with the following content:
#ifdef DEBUG_MEMORY
extern int handle_creation_count;
extern int handle_deletion_count;
#endif
- In one
.cpp
file (e.g.,OccHandleDebugCounters.cpp
), add:
#ifdef DEBUG_MEMORY
int handle_creation_count = 0;
int handle_deletion_count = 0;
#endif
- Make sure all files that use these counters include
OccHandleDebugCounters.h
instead of declaring their own.
PyErr_SetString(PyExc_RuntimeError, "Failed to downcast to TYPE."); | ||
return nullptr; | ||
// Plus d'information dans l'erreur | ||
PyErr_Format(PyExc_TypeError, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (bug_risk): Error in DownCast isn’t propagated to Python
Use SWIG_fail or SWIG_exception_fail after setting the Python error to ensure the exception is properly propagated.
process_opencascade_exception(error, "$name", "$parentclassname"); | ||
SWIG_fail; | ||
} | ||
catch(const std::bad_alloc& e) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (bug_risk): Add catch-all handlers for std::exception and ellipsis
Unhandled C++ exceptions may escape. Please add catch(const std::exception& e)
and a final catch(...)
to ensure all exceptions are converted to Python RuntimeError.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @tpaviot - I've reviewed your changes - here's some feedback:
- The string/bytes → stream conversion logic in IOStream.i is nearly identical for istream and stringstream—consider extracting the common encoding and error-checking steps into a helper to reduce duplication.
- The DEBUG_MEMORY counters and inline functions in OccHandle.i are defined in a header—ensure they aren’t multiply instantiated across translation units and consider making them thread-safe if used in multithreaded contexts.
- The exception-to-Python mapping in ExceptionCatcher.i could be simplified and made more maintainable by consolidating the type-name checks into a static lookup table or map rather than a long if/else chain.
Here's what I looked at during the review
- 🟡 General issues: 1 issue found
- 🟢 Security: all looks good
- 🟢 Testing: all looks good
- 🟢 Documentation: all looks good
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
%inline %{ | ||
static int handle_creation_count = 0; | ||
static int handle_deletion_count = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (bug_risk): DEBUG_MEMORY counters are not thread-safe
If these counters are updated from multiple threads, use atomic types like std::atomic to prevent data races.
%inline %{ | |
static int handle_creation_count = 0; | |
static int handle_deletion_count = 0; | |
%inline %{ | |
#include <atomic> | |
static std::atomic<int> handle_creation_count{0}; | |
static std::atomic<int> handle_deletion_count{0}; |
Summary by Sourcery
Modernize SWIG interface by dropping Python 2 support, refactoring IO stream conversions, and improving memory and exception management: add debug counters for handle ref tracking, refine handle utilities, map OpenCASCADE exceptions to precise Python errors, and update tests accordingly.
New Features:
Enhancements:
Tests: