patch 8.2.3788: lambda for option that is a function may be freed
Problem: Lambda for option that is a function may be garbage collected.
Solution: Set a reference in the funcref. (Yegappan Lakshmanan,
closes #9330)
diff --git a/src/testdir/test_iminsert.vim b/src/testdir/test_iminsert.vim
index a53e6f3..860fb07 100644
--- a/src/testdir/test_iminsert.vim
+++ b/src/testdir/test_iminsert.vim
@@ -119,83 +119,142 @@
let g:IMstatusfunc_called += 1
return 1
endfunc
- let g:IMactivatefunc_called = 0
- let g:IMstatusfunc_called = 0
set iminsert=2
- " Test for using a function()
- set imactivatefunc=function('IMactivatefunc1')
- set imstatusfunc=function('IMstatusfunc1')
- normal! i
+ let lines =<< trim END
+ LET g:IMactivatefunc_called = 0
+ LET g:IMstatusfunc_called = 0
- " Using a funcref variable to set 'completefunc'
- let Fn1 = function('IMactivatefunc1')
- let &imactivatefunc = Fn1
- let Fn2 = function('IMstatusfunc1')
- let &imstatusfunc = Fn2
- normal! i
+ #" Test for using a function()
+ set imactivatefunc=function('g:IMactivatefunc1')
+ set imstatusfunc=function('g:IMstatusfunc1')
+ normal! i
- " Using a string(funcref variable) to set 'completefunc'
- let &imactivatefunc = string(Fn1)
- let &imstatusfunc = string(Fn2)
- normal! i
+ #" Using a funcref variable to set 'completefunc'
+ VAR Fn1 = function('g:IMactivatefunc1')
+ LET &imactivatefunc = Fn1
+ VAR Fn2 = function('g:IMstatusfunc1')
+ LET &imstatusfunc = Fn2
+ normal! i
- " Test for using a funcref()
- set imactivatefunc=funcref('IMactivatefunc1')
- set imstatusfunc=funcref('IMstatusfunc1')
- normal! i
+ #" Using a string(funcref variable) to set 'completefunc'
+ LET &imactivatefunc = string(Fn1)
+ LET &imstatusfunc = string(Fn2)
+ normal! i
- " Using a funcref variable to set 'imactivatefunc'
- let Fn1 = funcref('IMactivatefunc1')
- let &imactivatefunc = Fn1
- let Fn2 = funcref('IMstatusfunc1')
- let &imstatusfunc = Fn2
- normal! i
+ #" Test for using a funcref()
+ set imactivatefunc=funcref('g:IMactivatefunc1')
+ set imstatusfunc=funcref('g:IMstatusfunc1')
+ normal! i
- " Using a string(funcref variable) to set 'imactivatefunc'
- let &imactivatefunc = string(Fn1)
- let &imstatusfunc = string(Fn2)
- normal! i
+ #" Using a funcref variable to set 'imactivatefunc'
+ LET Fn1 = funcref('g:IMactivatefunc1')
+ LET &imactivatefunc = Fn1
+ LET Fn2 = funcref('g:IMstatusfunc1')
+ LET &imstatusfunc = Fn2
+ normal! i
- " Test for using a lambda function
- set imactivatefunc={a\ ->\ IMactivatefunc1(a)}
- set imstatusfunc={\ ->\ IMstatusfunc1()}
- normal! i
+ #" Using a string(funcref variable) to set 'imactivatefunc'
+ LET &imactivatefunc = string(Fn1)
+ LET &imstatusfunc = string(Fn2)
+ normal! i
- " Set 'imactivatefunc' and 'imstatusfunc' to a lambda expression
- let &imactivatefunc = {a -> IMactivatefunc1(a)}
- let &imstatusfunc = { -> IMstatusfunc1()}
- normal! i
+ #" Test for using a lambda function
+ VAR optval = "LSTART a LMIDDLE IMactivatefunc1(a) LEND"
+ LET optval = substitute(optval, ' ', '\\ ', 'g')
+ exe "set imactivatefunc=" .. optval
+ LET optval = "LSTART LMIDDLE IMstatusfunc1() LEND"
+ LET optval = substitute(optval, ' ', '\\ ', 'g')
+ exe "set imstatusfunc=" .. optval
+ normal! i
- " Set 'imactivatefunc' and 'imstatusfunc' to a string(lambda expression)
- let &imactivatefunc = '{a -> IMactivatefunc1(a)}'
- let &imstatusfunc = '{ -> IMstatusfunc1()}'
- normal! i
+ #" Set 'imactivatefunc' and 'imstatusfunc' to a lambda expression
+ LET &imactivatefunc = LSTART a LMIDDLE IMactivatefunc1(a) LEND
+ LET &imstatusfunc = LSTART LMIDDLE IMstatusfunc1() LEND
+ normal! i
- " Set 'imactivatefunc' 'imstatusfunc' to a variable with a lambda expression
- let Lambda1 = {a -> IMactivatefunc1(a)}
- let Lambda2 = { -> IMstatusfunc1()}
- let &imactivatefunc = Lambda1
- let &imstatusfunc = Lambda2
- normal! i
+ #" Set 'imactivatefunc' and 'imstatusfunc' to a string(lambda expression)
+ LET &imactivatefunc = 'LSTART a LMIDDLE IMactivatefunc1(a) LEND'
+ LET &imstatusfunc = 'LSTART LMIDDLE IMstatusfunc1() LEND'
+ normal! i
- " Set 'imactivatefunc' 'imstatusfunc' to a string(variable with a lambda
- " expression)
- let &imactivatefunc = string(Lambda1)
- let &imstatusfunc = string(Lambda2)
- normal! i
+ #" Set 'imactivatefunc' 'imstatusfunc' to a variable with a lambda
+ #" expression
+ VAR Lambda1 = LSTART a LMIDDLE IMactivatefunc1(a) LEND
+ VAR Lambda2 = LSTART LMIDDLE IMstatusfunc1() LEND
+ LET &imactivatefunc = Lambda1
+ LET &imstatusfunc = Lambda2
+ normal! i
- " Test for clearing the 'completefunc' option
- set imactivatefunc='' imstatusfunc=''
- set imactivatefunc& imstatusfunc&
+ #" Set 'imactivatefunc' 'imstatusfunc' to a string(variable with a lambda
+ #" expression)
+ LET &imactivatefunc = string(Lambda1)
+ LET &imstatusfunc = string(Lambda2)
+ normal! i
- call assert_fails("set imactivatefunc=function('abc')", "E700:")
- call assert_fails("set imstatusfunc=function('abc')", "E700:")
- call assert_fails("set imactivatefunc=funcref('abc')", "E700:")
- call assert_fails("set imstatusfunc=funcref('abc')", "E700:")
+ #" Test for clearing the 'completefunc' option
+ set imactivatefunc='' imstatusfunc=''
+ set imactivatefunc& imstatusfunc&
- call assert_equal(11, g:IMactivatefunc_called)
- call assert_equal(22, g:IMstatusfunc_called)
+ set imactivatefunc=g:IMactivatefunc1
+ set imstatusfunc=g:IMstatusfunc1
+ call assert_fails("set imactivatefunc=function('abc')", "E700:")
+ call assert_fails("set imstatusfunc=function('abc')", "E700:")
+ call assert_fails("set imactivatefunc=funcref('abc')", "E700:")
+ call assert_fails("set imstatusfunc=funcref('abc')", "E700:")
+ call assert_fails("LET &imstatusfunc = function('abc')", "E700:")
+ call assert_fails("LET &imactivatefunc = function('abc')", "E700:")
+ normal! i
+
+ #" set 'imactivatefunc' and 'imstatusfunc' to a non-existing function
+ set imactivatefunc=IMactivatefunc1
+ set imstatusfunc=IMstatusfunc1
+ call assert_fails("set imactivatefunc=function('NonExistingFunc')", 'E700:')
+ call assert_fails("set imstatusfunc=function('NonExistingFunc')", 'E700:')
+ call assert_fails("LET &imactivatefunc = function('NonExistingFunc')", 'E700:')
+ call assert_fails("LET &imstatusfunc = function('NonExistingFunc')", 'E700:')
+ normal! i
+
+ call assert_equal(13, g:IMactivatefunc_called)
+ call assert_equal(26, g:IMstatusfunc_called)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ " Using Vim9 lambda expression in legacy context should fail
+ set imactivatefunc=(a)\ =>\ IMactivatefunc1(a)
+ set imstatusfunc=IMstatusfunc1
+ call assert_fails('normal! i', 'E117:')
+ set imactivatefunc=IMactivatefunc1
+ set imstatusfunc=()\ =>\ IMstatusfunc1(a)
+ call assert_fails('normal! i', 'E117:')
+
+ " set 'imactivatefunc' and 'imstatusfunc' to a partial with dict. This used
+ " to cause a crash.
+ func SetIMFunc()
+ let params1 = {'activate': function('g:DictActivateFunc')}
+ let params2 = {'status': function('g:DictStatusFunc')}
+ let &imactivatefunc = params1.activate
+ let &imstatusfunc = params2.status
+ endfunc
+ func g:DictActivateFunc(_) dict
+ endfunc
+ func g:DictStatusFunc(_) dict
+ endfunc
+ call SetIMFunc()
+ new
+ call SetIMFunc()
+ bw
+ call test_garbagecollect_now()
+ new
+ set imactivatefunc=
+ set imstatusfunc=
+ wincmd w
+ set imactivatefunc=
+ set imstatusfunc=
+ :%bw!
+ delfunc g:DictActivateFunc
+ delfunc g:DictStatusFunc
+ delfunc SetIMFunc
" Vim9 tests
let lines =<< trim END
@@ -217,59 +276,12 @@
set imstatusfunc=function('IMstatusfunc1')
normal! i
- # Test for using a lambda
- &imactivatefunc = '(a) => IMactivatefunc1(a)'
- &imstatusfunc = '() => IMstatusfunc1()'
- normal! i
-
- # Test for using a variable with a lambda expression
- var Fn1: func = (active) => {
- g:IMactivatefunc_called += 1
- return 1
- }
- var Fn2: func = () => {
- g:IMstatusfunc_called += 1
- return 1
- }
- &imactivatefunc = Fn1
- &imstatusfunc = Fn2
- normal! i
-
- # Test for using a string(variable with a lambda expression)
- &imactivatefunc = string(Fn1)
- &imstatusfunc = string(Fn2)
- normal! i
-
- assert_equal(4, g:IMactivatefunc_called)
- assert_equal(8, g:IMstatusfunc_called)
-
set iminsert=0
set imactivatefunc=
set imstatusfunc=
END
call CheckScriptSuccess(lines)
- " Using Vim9 lambda expression in legacy context should fail
- set imactivatefunc=(a)\ =>\ IMactivatefunc1(a)
- set imstatusfunc=IMstatusfunc1
- call assert_fails('normal! i', 'E117:')
- set imactivatefunc=IMactivatefunc1
- set imstatusfunc=()\ =>\ IMstatusfunc1(a)
- call assert_fails('normal! i', 'E117:')
-
- " set 'imactivatefunc' and 'imstatusfunc' to a non-existing function
- set imactivatefunc=IMactivatefunc1
- set imstatusfunc=IMstatusfunc1
- call assert_fails("set imactivatefunc=function('NonExistingFunc')", 'E700:')
- call assert_fails("set imstatusfunc=function('NonExistingFunc')", 'E700:')
- call assert_fails("let &imactivatefunc = function('NonExistingFunc')", 'E700:')
- call assert_fails("let &imstatusfunc = function('NonExistingFunc')", 'E700:')
- let g:IMactivatefunc_called = 0
- let g:IMstatusfunc_called = 0
- normal! i
- call assert_equal(2, g:IMactivatefunc_called)
- call assert_equal(2, g:IMstatusfunc_called)
-
" cleanup
delfunc IMactivatefunc1
delfunc IMstatusfunc1