mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-06-13 17:43:29 +03:00
Merge pull request #1114 from benoit-pierre/fix-webp-memory-leak
Fix WebP memory leaks
This commit is contained in:
commit
51eb8b6237
37
Tests/check_webp_leaks.py
Normal file
37
Tests/check_webp_leaks.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
from helper import unittest, PillowTestCase
|
||||||
|
import sys
|
||||||
|
from PIL import Image
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
# Limits for testing the leak
|
||||||
|
mem_limit = 16 # max increase in MB
|
||||||
|
iterations = 5000
|
||||||
|
test_file = "Tests/images/hopper.webp"
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or MacOS")
|
||||||
|
class TestWebPLeaks(PillowTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
try:
|
||||||
|
from PIL import _webp
|
||||||
|
except ImportError:
|
||||||
|
self.skipTest('WebP support not installed')
|
||||||
|
|
||||||
|
def _get_mem_usage(self):
|
||||||
|
from resource import getpagesize, getrusage, RUSAGE_SELF
|
||||||
|
mem = getrusage(RUSAGE_SELF).ru_maxrss
|
||||||
|
return mem * getpagesize() / 1024 / 1024
|
||||||
|
|
||||||
|
def test_leak_load(self):
|
||||||
|
with open(test_file, 'rb') as f:
|
||||||
|
im_data = f.read()
|
||||||
|
start_mem = self._get_mem_usage()
|
||||||
|
for count in range(iterations):
|
||||||
|
with Image.open(BytesIO(im_data)) as im:
|
||||||
|
im.load()
|
||||||
|
mem = (self._get_mem_usage() - start_mem)
|
||||||
|
self.assertLess(mem, mem_limit, msg='memory usage limit exceeded')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
46
_webp.c
46
_webp.c
|
@ -136,9 +136,9 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args)
|
||||||
PyObject* WebPDecode_wrapper(PyObject* self, PyObject* args)
|
PyObject* WebPDecode_wrapper(PyObject* self, PyObject* args)
|
||||||
{
|
{
|
||||||
PyBytesObject *webp_string;
|
PyBytesObject *webp_string;
|
||||||
uint8_t *webp;
|
const uint8_t *webp;
|
||||||
Py_ssize_t size;
|
Py_ssize_t size;
|
||||||
PyObject *ret, *bytes, *pymode, *icc_profile = Py_None, *exif = Py_None;
|
PyObject *ret = Py_None, *bytes = NULL, *pymode = NULL, *icc_profile = NULL, *exif = NULL;
|
||||||
WebPDecoderConfig config;
|
WebPDecoderConfig config;
|
||||||
VP8StatusCode vp8_status_code = VP8_STATUS_OK;
|
VP8StatusCode vp8_status_code = VP8_STATUS_OK;
|
||||||
char* mode = "RGB";
|
char* mode = "RGB";
|
||||||
|
@ -173,31 +173,34 @@ PyObject* WebPDecode_wrapper(PyObject* self, PyObject* args)
|
||||||
WebPData exif_data = {0};
|
WebPData exif_data = {0};
|
||||||
|
|
||||||
WebPMux* mux = WebPMuxCreate(&data, copy_data);
|
WebPMux* mux = WebPMuxCreate(&data, copy_data);
|
||||||
WebPMuxGetFrame(mux, 1, &image);
|
if (NULL == mux)
|
||||||
webp = (uint8_t*)image.bitstream.bytes;
|
goto end;
|
||||||
|
|
||||||
|
if (WEBP_MUX_OK != WebPMuxGetFrame(mux, 1, &image))
|
||||||
|
{
|
||||||
|
WebPMuxDelete(mux);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
webp = image.bitstream.bytes;
|
||||||
size = image.bitstream.size;
|
size = image.bitstream.size;
|
||||||
|
|
||||||
vp8_status_code = WebPDecode(webp, size, &config);
|
vp8_status_code = WebPDecode(webp, size, &config);
|
||||||
|
|
||||||
WebPMuxGetChunk(mux, "ICCP", &icc_profile_data);
|
if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "ICCP", &icc_profile_data))
|
||||||
if (icc_profile_data.size > 0) {
|
|
||||||
icc_profile = PyBytes_FromStringAndSize((const char*)icc_profile_data.bytes, icc_profile_data.size);
|
icc_profile = PyBytes_FromStringAndSize((const char*)icc_profile_data.bytes, icc_profile_data.size);
|
||||||
}
|
|
||||||
|
|
||||||
WebPMuxGetChunk(mux, "EXIF", &exif_data);
|
if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "EXIF", &exif_data))
|
||||||
if (exif_data.size > 0) {
|
|
||||||
exif = PyBytes_FromStringAndSize((const char*)exif_data.bytes, exif_data.size);
|
exif = PyBytes_FromStringAndSize((const char*)exif_data.bytes, exif_data.size);
|
||||||
}
|
|
||||||
|
|
||||||
|
WebPDataClear(&image.bitstream);
|
||||||
WebPMuxDelete(mux);
|
WebPMuxDelete(mux);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vp8_status_code != VP8_STATUS_OK) {
|
if (vp8_status_code != VP8_STATUS_OK)
|
||||||
WebPFreeDecBuffer(&config.output);
|
goto end;
|
||||||
Py_RETURN_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.output.colorspace < MODE_YUV) {
|
if (config.output.colorspace < MODE_YUV) {
|
||||||
bytes = PyBytes_FromStringAndSize((char *)config.output.u.RGBA.rgba,
|
bytes = PyBytes_FromStringAndSize((char *)config.output.u.RGBA.rgba,
|
||||||
|
@ -215,8 +218,21 @@ PyObject* WebPDecode_wrapper(PyObject* self, PyObject* args)
|
||||||
pymode = PyString_FromString(mode);
|
pymode = PyString_FromString(mode);
|
||||||
#endif
|
#endif
|
||||||
ret = Py_BuildValue("SiiSSS", bytes, config.output.width,
|
ret = Py_BuildValue("SiiSSS", bytes, config.output.width,
|
||||||
config.output.height, pymode, icc_profile, exif);
|
config.output.height, pymode,
|
||||||
|
NULL == icc_profile ? Py_None : icc_profile,
|
||||||
|
NULL == exif ? Py_None : exif);
|
||||||
|
|
||||||
|
end:
|
||||||
WebPFreeDecBuffer(&config.output);
|
WebPFreeDecBuffer(&config.output);
|
||||||
|
|
||||||
|
Py_XDECREF(bytes);
|
||||||
|
Py_XDECREF(pymode);
|
||||||
|
Py_XDECREF(icc_profile);
|
||||||
|
Py_XDECREF(exif);
|
||||||
|
|
||||||
|
if (Py_None == ret)
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user