From 423a85a11a9d3d658906aea715fed7fe6aa83cd8 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Sat, 29 Aug 2020 12:57:16 +0200 Subject: patch 8.2.1538: Python: iteration over vim objects fails to keep reference Problem: Python: iteration over vim objects fails to keep reference. Solution: Keep a reference for the object. (Paul Ollis, closes #6803, closes #6806) --- src/if_py_both.h | 20 ++++++++++++----- src/testdir/test_python3.vim | 52 ++++++++++++++++++++++++++++++++++++++++++++ src/version.c | 2 ++ 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/src/if_py_both.h b/src/if_py_both.h index 8eb77470a3..86942b686c 100644 --- a/src/if_py_both.h +++ b/src/if_py_both.h @@ -1442,11 +1442,12 @@ typedef struct destructorfun destruct; traversefun traverse; clearfun clear; + PyObject *iter_object; } IterObject; static PyObject * IterNew(void *start, destructorfun destruct, nextfun next, traversefun traverse, - clearfun clear) + clearfun clear, PyObject *iter_object) { IterObject *self; @@ -1456,6 +1457,10 @@ IterNew(void *start, destructorfun destruct, nextfun next, traversefun traverse, self->destruct = destruct; self->traverse = traverse; self->clear = clear; + self->iter_object = iter_object; + + if (iter_object) + Py_INCREF(iter_object); return (PyObject *)(self); } @@ -1463,6 +1468,8 @@ IterNew(void *start, destructorfun destruct, nextfun next, traversefun traverse, static void IterDestructor(IterObject *self) { + if (self->iter_object) + Py_DECREF(self->iter_object); PyObject_GC_UnTrack((void *)(self)); self->destruct(self->cur); PyObject_GC_Del((void *)(self)); @@ -1844,7 +1851,7 @@ DictionaryIter(DictionaryObject *self) return IterNew(dii, (destructorfun) PyMem_Free, (nextfun) DictionaryIterNext, - NULL, NULL); + NULL, NULL, (PyObject *)self); } static PyInt @@ -2842,7 +2849,7 @@ ListIter(ListObject *self) return IterNew(lii, (destructorfun) ListIterDestruct, (nextfun) ListIterNext, - NULL, NULL); + NULL, NULL, (PyObject *)self); } static char *ListAttrs[] = { @@ -3491,7 +3498,7 @@ OptionsIter(OptionsObject *self) return IterNew(oii, (destructorfun) PyMem_Free, (nextfun) OptionsIterNext, - NULL, NULL); + NULL, NULL, (PyObject *)self); } static int @@ -5488,14 +5495,15 @@ BufMapIterNext(PyObject **buffer) } static PyObject * -BufMapIter(PyObject *self UNUSED) +BufMapIter(PyObject *self) { PyObject *buffer; buffer = BufferNew(firstbuf); return IterNew(buffer, (destructorfun) BufMapIterDestruct, (nextfun) BufMapIterNext, - (traversefun) BufMapIterTraverse, (clearfun) BufMapIterClear); + (traversefun) BufMapIterTraverse, (clearfun) BufMapIterClear, + (PyObject *)self); } static PyMappingMethods BufMapAsMapping = { diff --git a/src/testdir/test_python3.vim b/src/testdir/test_python3.vim index 830fbba50c..0885c96988 100644 --- a/src/testdir/test_python3.vim +++ b/src/testdir/test_python3.vim @@ -4,6 +4,15 @@ source check.vim CheckFeature python3 source shared.vim +func Create_vim_list() + return [1] +endfunction + +func Create_vim_dict() + return {'a': 1} +endfunction + + " This function should be called first. This sets up python functions used by " the other tests. func Test_AAA_python3_setup() @@ -3944,4 +3953,47 @@ func Test_python3_keyboard_interrupt() close! endfunc +" Regression: Iterator for a Vim object should hold a reference. +func Test_python3_iter_ref() + let g:list_iter_ref_count_increase = -1 + let g:dict_iter_ref_count_increase = -1 + let g:bufmap_iter_ref_count_increase = -1 + let g:options_iter_ref_count_increase = -1 + + py3 << trim EOF + import sys + import vim + + def test_python3_iter_ref(): + create_list = vim.Function('Create_vim_list') + v = create_list() + base_ref_count = sys.getrefcount(v) + for el in v: + vim.vars['list_iter_ref_count_increase'] = sys.getrefcount(v) - base_ref_count + + create_dict = vim.Function('Create_vim_dict') + v = create_dict() + base_ref_count = sys.getrefcount(v) + for el in v: + vim.vars['dict_iter_ref_count_increase'] = sys.getrefcount(v) - base_ref_count + + v = vim.buffers + base_ref_count = sys.getrefcount(v) + for el in v: + vim.vars['bufmap_iter_ref_count_increase'] = sys.getrefcount(v) - base_ref_count + + v = vim.options + base_ref_count = sys.getrefcount(v) + for el in v: + vim.vars['options_iter_ref_count_increase'] = sys.getrefcount(v) - base_ref_count + + test_python3_iter_ref() + EOF + + call assert_equal(1, g:list_iter_ref_count_increase) + call assert_equal(1, g:dict_iter_ref_count_increase) + call assert_equal(1, g:bufmap_iter_ref_count_increase) + call assert_equal(1, g:options_iter_ref_count_increase) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c index d4d525bbdd..9e3430a106 100644 --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1538, /**/ 1537, /**/ -- cgit v1.2.3