Skip to content

Commit 562c3a9

Browse files
Release memoryview when some methods are called
This helps prevent problems with a memoryview pointing at data that is no longer valid. Destructors do not release the view.
1 parent aa3ae6a commit 562c3a9

24 files changed

+903
-168
lines changed

src/interface/basicio.i

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,12 @@ OUTPUT_BUFFER_RW(Exiv2::byte* buf, size_t rcount)
135135
RETURN_VIEW(Exiv2::byte* mmap, $1 ? arg1->size() : 0,
136136
arg2 ? PyBUF_WRITE : PyBUF_READ,)
137137

138+
// Release memoryview when some functions are called
139+
%typemap(ret, fragment="release_view")
140+
(int close), (int munmap), (long write), (size_t write) %{
141+
release_view(self);
142+
%}
143+
138144
// Enable len(Exiv2::BasicIo)
139145
%feature("python:slot", "sq_length", functype="lenfunc")
140146
Exiv2::BasicIo::size;

src/interface/shared/buffers.i

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1717

1818

19+
%include "shared/keep_reference.i"
20+
1921
// Macro for input read only byte buffer
2022
%define INPUT_BUFFER_RO(buf_type, len_type)
2123
%typemap(doctype) buf_type ":py:term:`bytes-like object`";
@@ -77,12 +79,34 @@ INPUT_BUFFER_RO(buf_type, len_type)
7779
%}
7880
%enddef // OUTPUT_BUFFER_RW
7981

82+
// Function to release any memoryview held in a weak ref
83+
%fragment("release_view", "header", fragment="private_data") {
84+
static int release_view(PyObject* py_self) {
85+
PyObject* ref = fetch_private(py_self, "view");
86+
if (!ref)
87+
return 0;
88+
PyObject* view = PyWeakref_GetObject(ref);
89+
if (PyMemoryView_Check(view))
90+
Py_XDECREF(PyObject_CallMethod(view, "release", NULL));
91+
Py_DECREF(ref);
92+
return 0;
93+
};
94+
}
95+
8096
// Macro to convert byte* return value to memoryview
8197
// WARNING: return value does not keep a reference to the data it points to
8298
%define RETURN_VIEW(signature, size_func, flags, doc_method)
8399
%typemap(doctype) signature "memoryview";
84-
%typemap(out) (signature) %{
100+
%typemap(out, fragment="private_data",
101+
fragment="release_view") (signature) %{
85102
$result = PyMemoryView_FromMemory((char*)$1, size_func, flags);
103+
if (!$result)
104+
SWIG_fail;
105+
// Release any existing memoryview
106+
release_view(self);
107+
// Store a weak ref to the new memoryview
108+
if (store_private(self, "view", PyWeakref_NewRef($result, NULL), true))
109+
SWIG_fail;
86110
%}
87111
#if #doc_method != ""
88112
%feature("docstring") doc_method

src/interface/shared/keep_reference.i

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818

1919
// Functions to store and retrieve "private" data attached to Pyhon object
2020
%fragment("private_data", "header") {
21-
static PyObject* _get_store(PyObject* py_self) {
21+
static PyObject* _get_store(PyObject* py_self, bool create) {
2222
if (!PyObject_HasAttrString(py_self, "_private_data_")) {
23+
if (!create)
24+
return NULL;
2325
PyObject* dict = PyDict_New();
2426
if (!dict)
2527
return NULL;
@@ -31,15 +33,31 @@ static PyObject* _get_store(PyObject* py_self) {
3133
return PyObject_GetAttrString(py_self, "_private_data_");
3234
};
3335
static int store_private(PyObject* py_self, const char* name,
34-
PyObject* val) {
35-
PyObject* dict = _get_store(py_self);
36-
if (!dict)
37-
return -1;
36+
PyObject* val, bool take_ownership=false) {
3837
int result = 0;
39-
if (val)
40-
result = PyDict_SetItemString(dict, name, val);
41-
else if (PyDict_GetItemString(dict, name))
42-
result = PyDict_DelItemString(dict, name);
38+
PyObject* dict = _get_store(py_self, true);
39+
if (dict) {
40+
if (val)
41+
result = PyDict_SetItemString(dict, name, val);
42+
else if (PyDict_GetItemString(dict, name))
43+
result = PyDict_DelItemString(dict, name);
44+
Py_DECREF(dict);
45+
}
46+
else
47+
result = -1;
48+
if (take_ownership && val)
49+
Py_DECREF(val);
50+
return result;
51+
};
52+
static PyObject* fetch_private(PyObject* py_self, const char* name) {
53+
PyObject* dict = _get_store(py_self, false);
54+
if (!dict)
55+
return NULL;
56+
PyObject* result = PyDict_GetItemString(dict, name);
57+
if (result) {
58+
Py_INCREF(result);
59+
PyDict_DelItemString(dict, name);
60+
}
4361
Py_DECREF(dict);
4462
return result;
4563
};

src/interface/types.i

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// python-exiv2 - Python interface to libexiv2
22
// http://github.com/jim-easterbrook/python-exiv2
3-
// Copyright (C) 2021-24 Jim Easterbrook [email protected]
3+
// Copyright (C) 2021-25 Jim Easterbrook [email protected]
44
//
55
// This program is free software: you can redistribute it and/or modify
66
// it under the terms of the GNU General Public License as published by
@@ -35,10 +35,12 @@ EXCEPTION()
3535

3636
// Some calls don't raise exceptions
3737
%noexception Exiv2::DataBuf::data;
38-
%noexception Exiv2::DataBuf::free;
3938
%noexception Exiv2::DataBuf::reset;
4039
%noexception Exiv2::DataBuf::size;
4140
%noexception Exiv2::DataBuf::__len__;
41+
#if EXIV2_VERSION_HEX < 0x001c0000
42+
%noexception Exiv2::DataBuf::free;
43+
#endif
4244

4345
// Function to set location of localisation files
4446
// (types.hpp includes exiv2's localisation stuff)
@@ -243,6 +245,18 @@ RETURN_VIEW(Exiv2::byte* pData_, arg1->DATABUF_SIZE, PyBUF_WRITE,
243245
RETURN_VIEW(Exiv2::byte* data, arg1->DATABUF_SIZE, PyBUF_WRITE,
244246
Exiv2::DataBuf::data)
245247

248+
// Release memoryview when other functions are called
249+
%typemap(ret, fragment="release_view")
250+
(void alloc), (void reset), (void resize) %{
251+
release_view(self);
252+
%}
253+
#if EXIV2_VERSION_HEX < 0x001c0000
254+
%typemap(ret, fragment="release_view")
255+
(void free) %{
256+
release_view(self);
257+
%}
258+
#endif
259+
246260
#if EXIV2_VERSION_HEX < 0x001c0000
247261
// Backport Exiv2 v0.28.0 methods
248262
%extend Exiv2::DataBuf {

src/interface/value.i

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,10 @@ RETURN_VIEW(const char* data, arg1->value_.size(), PyBUF_READ,
507507
return $self->value_.data();
508508
}
509509
}
510+
// Release memoryview when new data is read
511+
%typemap(ret, fragment="release_view") (int class::read) %{
512+
release_view(self);
513+
%}
510514
%enddef // RAW_STRING_DATA
511515
RAW_STRING_DATA(Exiv2::StringValueBase)
512516
RAW_STRING_DATA(Exiv2::XmpTextValue)

src/swig-0_27_7/basicio_wrap.cxx

Lines changed: 76 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4526,6 +4526,63 @@ SWIGINTERNINLINE PyObject*
45264526
}
45274527

45284528

4529+
static PyObject* _get_store(PyObject* py_self, bool create) {
4530+
if (!PyObject_HasAttrString(py_self, "_private_data_")) {
4531+
if (!create)
4532+
return NULL;
4533+
PyObject* dict = PyDict_New();
4534+
if (!dict)
4535+
return NULL;
4536+
int error = PyObject_SetAttrString(py_self, "_private_data_", dict);
4537+
Py_DECREF(dict);
4538+
if (error)
4539+
return NULL;
4540+
}
4541+
return PyObject_GetAttrString(py_self, "_private_data_");
4542+
};
4543+
static int store_private(PyObject* py_self, const char* name,
4544+
PyObject* val, bool take_ownership=false) {
4545+
int result = 0;
4546+
PyObject* dict = _get_store(py_self, true);
4547+
if (dict) {
4548+
if (val)
4549+
result = PyDict_SetItemString(dict, name, val);
4550+
else if (PyDict_GetItemString(dict, name))
4551+
result = PyDict_DelItemString(dict, name);
4552+
Py_DECREF(dict);
4553+
}
4554+
else
4555+
result = -1;
4556+
if (take_ownership && val)
4557+
Py_DECREF(val);
4558+
return result;
4559+
};
4560+
static PyObject* fetch_private(PyObject* py_self, const char* name) {
4561+
PyObject* dict = _get_store(py_self, false);
4562+
if (!dict)
4563+
return NULL;
4564+
PyObject* result = PyDict_GetItemString(dict, name);
4565+
if (result) {
4566+
Py_INCREF(result);
4567+
PyDict_DelItemString(dict, name);
4568+
}
4569+
Py_DECREF(dict);
4570+
return result;
4571+
};
4572+
4573+
4574+
static int release_view(PyObject* py_self) {
4575+
PyObject* ref = fetch_private(py_self, "view");
4576+
if (!ref)
4577+
return 0;
4578+
PyObject* view = PyWeakref_GetObject(ref);
4579+
if (PyMemoryView_Check(view))
4580+
Py_XDECREF(PyObject_CallMethod(view, "release", NULL));
4581+
Py_DECREF(ref);
4582+
return 0;
4583+
};
4584+
4585+
45294586
SWIGINTERN int
45304587
SWIG_AsVal_double (PyObject *obj, double *val)
45314588
{
@@ -4831,33 +4888,6 @@ SWIGINTERN char const *Exiv2_BasicIo_ioType(Exiv2::BasicIo *self){
48314888
SWIGINTERN DataContext *Exiv2_BasicIo_data(Exiv2::BasicIo *self,bool isWriteable){
48324889
return new DataContext(self, isWriteable);
48334890
}
4834-
4835-
static PyObject* _get_store(PyObject* py_self) {
4836-
if (!PyObject_HasAttrString(py_self, "_private_data_")) {
4837-
PyObject* dict = PyDict_New();
4838-
if (!dict)
4839-
return NULL;
4840-
int error = PyObject_SetAttrString(py_self, "_private_data_", dict);
4841-
Py_DECREF(dict);
4842-
if (error)
4843-
return NULL;
4844-
}
4845-
return PyObject_GetAttrString(py_self, "_private_data_");
4846-
};
4847-
static int store_private(PyObject* py_self, const char* name,
4848-
PyObject* val) {
4849-
PyObject* dict = _get_store(py_self);
4850-
if (!dict)
4851-
return -1;
4852-
int result = 0;
4853-
if (val)
4854-
result = PyDict_SetItemString(dict, name, val);
4855-
else if (PyDict_GetItemString(dict, name))
4856-
result = PyDict_DelItemString(dict, name);
4857-
Py_DECREF(dict);
4858-
return result;
4859-
};
4860-
48614891
#ifdef __cplusplus
48624892
extern "C" {
48634893
#endif
@@ -5063,6 +5093,9 @@ SWIGINTERN PyObject *_wrap_BasicIo_close(PyObject *self, PyObject *args) {
50635093
}
50645094
}
50655095
resultobj = SWIG_From_int(static_cast< int >(result));
5096+
5097+
release_view(self);
5098+
50665099
return resultobj;
50675100
fail:
50685101
return NULL;
@@ -5114,6 +5147,9 @@ SWIGINTERN PyObject *_wrap_BasicIo_write__SWIG_0(PyObject *self, PyObject *args)
51145147

51155148
Py_XDECREF(_global_view);
51165149

5150+
5151+
release_view(self);
5152+
51175153
return resultobj;
51185154
fail:
51195155

@@ -5162,6 +5198,9 @@ SWIGINTERN PyObject *_wrap_BasicIo_write__SWIG_1(PyObject *self, PyObject *args)
51625198
}
51635199
}
51645200
resultobj = SWIG_From_long(static_cast< long >(result));
5201+
5202+
release_view(self);
5203+
51655204
return resultobj;
51665205
fail:
51675206
return NULL;
@@ -5571,6 +5610,13 @@ SWIGINTERN PyObject *_wrap_BasicIo_mmap(PyObject *self, PyObject *args) {
55715610
}
55725611

55735612
resultobj = PyMemoryView_FromMemory((char*)result, result ? arg1->size() : 0, arg2 ? PyBUF_WRITE : PyBUF_READ);
5613+
if (!resultobj)
5614+
SWIG_fail;
5615+
// Release any existing memoryview
5616+
release_view(self);
5617+
// Store a weak ref to the new memoryview
5618+
if (store_private(self, "view", PyWeakref_NewRef(resultobj, NULL), true))
5619+
SWIG_fail;
55745620

55755621
return resultobj;
55765622
fail:
@@ -5605,6 +5651,9 @@ SWIGINTERN PyObject *_wrap_BasicIo_munmap(PyObject *self, PyObject *args) {
56055651
}
56065652
}
56075653
resultobj = SWIG_From_int(static_cast< int >(result));
5654+
5655+
release_view(self);
5656+
56085657
return resultobj;
56095658
fail:
56105659
return NULL;

src/swig-0_27_7/exif_wrap.cxx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4429,8 +4429,10 @@ class ExifData_iterator : public ExifData_iterator_base {
44294429
};
44304430

44314431

4432-
static PyObject* _get_store(PyObject* py_self) {
4432+
static PyObject* _get_store(PyObject* py_self, bool create) {
44334433
if (!PyObject_HasAttrString(py_self, "_private_data_")) {
4434+
if (!create)
4435+
return NULL;
44344436
PyObject* dict = PyDict_New();
44354437
if (!dict)
44364438
return NULL;
@@ -4442,15 +4444,31 @@ static PyObject* _get_store(PyObject* py_self) {
44424444
return PyObject_GetAttrString(py_self, "_private_data_");
44434445
};
44444446
static int store_private(PyObject* py_self, const char* name,
4445-
PyObject* val) {
4446-
PyObject* dict = _get_store(py_self);
4447-
if (!dict)
4448-
return -1;
4447+
PyObject* val, bool take_ownership=false) {
44494448
int result = 0;
4450-
if (val)
4451-
result = PyDict_SetItemString(dict, name, val);
4452-
else if (PyDict_GetItemString(dict, name))
4453-
result = PyDict_DelItemString(dict, name);
4449+
PyObject* dict = _get_store(py_self, true);
4450+
if (dict) {
4451+
if (val)
4452+
result = PyDict_SetItemString(dict, name, val);
4453+
else if (PyDict_GetItemString(dict, name))
4454+
result = PyDict_DelItemString(dict, name);
4455+
Py_DECREF(dict);
4456+
}
4457+
else
4458+
result = -1;
4459+
if (take_ownership && val)
4460+
Py_DECREF(val);
4461+
return result;
4462+
};
4463+
static PyObject* fetch_private(PyObject* py_self, const char* name) {
4464+
PyObject* dict = _get_store(py_self, false);
4465+
if (!dict)
4466+
return NULL;
4467+
PyObject* result = PyDict_GetItemString(dict, name);
4468+
if (result) {
4469+
Py_INCREF(result);
4470+
PyDict_DelItemString(dict, name);
4471+
}
44544472
Py_DECREF(dict);
44554473
return result;
44564474
};

0 commit comments

Comments
 (0)