# HG changeset patch # User Bram Moolenaar # Date 1385654683 -3600 # Node ID 064e2a080e2e158177acc017c318bc953fb7535b # Parent a0b7edaf6a92ebb1ad4ae3600407626e363940c2 updated for version 7.4.107 Problem: Python: When vim.eval() encounters a Vim error, a try/catch in the Python code doesn't catch it. (Yggdroot Chen) Solution: Throw exceptions on errors in vim.eval(). (ZyX) diff --git a/src/ex_eval.c b/src/ex_eval.c --- a/src/ex_eval.c +++ b/src/ex_eval.c @@ -321,6 +321,17 @@ free_msglist(l) } /* + * Free global "*msg_list" and the messages it contains, then set "*msg_list" + * to NULL. + */ + void +free_global_msglist() +{ + free_msglist(*msg_list); + *msg_list = NULL; +} + +/* * Throw the message specified in the call to cause_errthrow() above as an * error exception. If cstack is NULL, postpone the throw until do_cmdline() * has returned (see do_one_cmd()). @@ -410,66 +421,41 @@ do_intthrow(cstack) return TRUE; } - /* - * Throw a new exception. Return FAIL when out of memory or it was tried to - * throw an illegal user exception. "value" is the exception string for a user - * or interrupt exception, or points to a message list in case of an error - * exception. + * Get an exception message that is to be stored in current_exception->value. */ - static int -throw_exception(value, type, cmdname) + char_u * +get_exception_string(value, type, cmdname, should_free) void *value; int type; char_u *cmdname; + int *should_free; { - except_T *excp; - char_u *p, *mesg, *val; + char_u *ret, *mesg; int cmdlen; - - /* - * Disallow faking Interrupt or error exceptions as user exceptions. They - * would be treated differently from real interrupt or error exceptions when - * no active try block is found, see do_cmdline(). - */ - if (type == ET_USER) - { - if (STRNCMP((char_u *)value, "Vim", 3) == 0 && - (((char_u *)value)[3] == NUL || ((char_u *)value)[3] == ':' || - ((char_u *)value)[3] == '(')) - { - EMSG(_("E608: Cannot :throw exceptions with 'Vim' prefix")); - goto fail; - } - } - - excp = (except_T *)alloc((unsigned)sizeof(except_T)); - if (excp == NULL) - goto nomem; + char_u *p, *val; if (type == ET_ERROR) { - /* Store the original message and prefix the exception value with - * "Vim:" or, if a command name is given, "Vim(cmdname):". */ - excp->messages = (struct msglist *)value; - mesg = excp->messages->throw_msg; + *should_free = FALSE; + mesg = ((struct msglist *)value)->throw_msg; if (cmdname != NULL && *cmdname != NUL) { cmdlen = (int)STRLEN(cmdname); - excp->value = vim_strnsave((char_u *)"Vim(", + ret = vim_strnsave((char_u *)"Vim(", 4 + cmdlen + 2 + (int)STRLEN(mesg)); - if (excp->value == NULL) - goto nomem; - STRCPY(&excp->value[4], cmdname); - STRCPY(&excp->value[4 + cmdlen], "):"); - val = excp->value + 4 + cmdlen + 2; + if (ret == NULL) + return ret; + STRCPY(&ret[4], cmdname); + STRCPY(&ret[4 + cmdlen], "):"); + val = ret + 4 + cmdlen + 2; } else { - excp->value = vim_strnsave((char_u *)"Vim:", 4 + (int)STRLEN(mesg)); - if (excp->value == NULL) - goto nomem; - val = excp->value + 4; + ret = vim_strnsave((char_u *)"Vim:", 4 + (int)STRLEN(mesg)); + if (ret == NULL) + return ret; + val = ret + 4; } /* msg_add_fname may have been used to prefix the message with a file @@ -506,14 +492,65 @@ throw_exception(value, type, cmdname) } } else - excp->value = value; + { + *should_free = FALSE; + ret = (char_u *) value; + } + + return ret; +} + + +/* + * Throw a new exception. Return FAIL when out of memory or it was tried to + * throw an illegal user exception. "value" is the exception string for a + * user or interrupt exception, or points to a message list in case of an + * error exception. + */ + static int +throw_exception(value, type, cmdname) + void *value; + int type; + char_u *cmdname; +{ + except_T *excp; + int should_free; + + /* + * Disallow faking Interrupt or error exceptions as user exceptions. They + * would be treated differently from real interrupt or error exceptions + * when no active try block is found, see do_cmdline(). + */ + if (type == ET_USER) + { + if (STRNCMP((char_u *)value, "Vim", 3) == 0 + && (((char_u *)value)[3] == NUL || ((char_u *)value)[3] == ':' + || ((char_u *)value)[3] == '(')) + { + EMSG(_("E608: Cannot :throw exceptions with 'Vim' prefix")); + goto fail; + } + } + + excp = (except_T *)alloc((unsigned)sizeof(except_T)); + if (excp == NULL) + goto nomem; + + if (type == ET_ERROR) + /* Store the original message and prefix the exception value with + * "Vim:" or, if a command name is given, "Vim(cmdname):". */ + excp->messages = (struct msglist *)value; + + excp->value = get_exception_string(value, type, cmdname, &should_free); + if (excp->value == NULL && should_free) + goto nomem; excp->type = type; excp->throw_name = vim_strsave(sourcing_name == NULL ? (char_u *)"" : sourcing_name); if (excp->throw_name == NULL) { - if (type == ET_ERROR) + if (should_free) vim_free(excp->value); goto nomem; } @@ -2033,10 +2070,7 @@ leave_cleanup(csp) /* If an error was about to be converted to an exception when * enter_cleanup() was called, free the message list. */ if (msg_list != NULL) - { - free_msglist(*msg_list); - *msg_list = NULL; - } + free_global_msglist(); } /* diff --git a/src/if_py_both.h b/src/if_py_both.h --- a/src/if_py_both.h +++ b/src/if_py_both.h @@ -566,6 +566,28 @@ VimTryEnd(void) PyErr_SetNone(PyExc_KeyboardInterrupt); return -1; } + else if (msg_list != NULL && *msg_list != NULL) + { + int should_free; + char_u *msg; + + msg = get_exception_string(*msg_list, ET_ERROR, NULL, &should_free); + + if (msg == NULL) + { + PyErr_NoMemory(); + return -1; + } + + PyErr_SetVim((char *) msg); + + free_global_msglist(); + + if (should_free) + vim_free(msg); + + return -1; + } else if (!did_throw) return (PyErr_Occurred() ? -1 : 0); /* Python exception is preferred over vim one; unlikely to occur though */ diff --git a/src/proto/ex_eval.pro b/src/proto/ex_eval.pro --- a/src/proto/ex_eval.pro +++ b/src/proto/ex_eval.pro @@ -4,8 +4,10 @@ void update_force_abort __ARGS((void)); int should_abort __ARGS((int retcode)); int aborted_in_try __ARGS((void)); int cause_errthrow __ARGS((char_u *mesg, int severe, int *ignore)); +void free_global_msglist __ARGS((void)); void do_errthrow __ARGS((struct condstack *cstack, char_u *cmdname)); int do_intthrow __ARGS((struct condstack *cstack)); +char_u *get_exception_string __ARGS((void *value, int type, char_u *cmdname, int *should_free)); void discard_current_exception __ARGS((void)); void report_make_pending __ARGS((int pending, void *value)); void report_resume_pending __ARGS((int pending, void *value)); diff --git a/src/testdir/test86.in b/src/testdir/test86.in --- a/src/testdir/test86.in +++ b/src/testdir/test86.in @@ -179,6 +179,32 @@ EOF :unlockvar! l :" :" Function calls +py << EOF +import sys +def ee(expr, g=globals(), l=locals()): + try: + exec(expr, g, l) + except: + ei = sys.exc_info() + msg = sys.exc_info()[0].__name__ + ':' + repr(sys.exc_info()[1].args) + msg = msg.replace('TypeError:(\'argument 1 ', 'TypeError:(\'') + if expr.find('None') > -1: + msg = msg.replace('TypeError:(\'iteration over non-sequence\',)', + 'TypeError:("\'NoneType\' object is not iterable",)') + if expr.find('FailingNumber') > -1: + msg = msg.replace(', not \'FailingNumber\'', '').replace('"', '\'') + msg = msg.replace('TypeError:(\'iteration over non-sequence\',)', + 'TypeError:("\'FailingNumber\' object is not iterable",)') + if msg.find('(\'\'') > -1 or msg.find('(\'can\'t') > -1: + msg = msg.replace('(\'', '("').replace('\',)', '",)') + if expr == 'fd(self=[])': + # HACK: PyMapping_Check changed meaning + msg = msg.replace('AttributeError:(\'keys\',)', + 'TypeError:(\'unable to convert list to vim dictionary\',)') + vim.current.buffer.append(expr + ':' + msg) + else: + vim.current.buffer.append(expr + ':NOT FAILED') +EOF :fun New(...) : return ['NewStart']+a:000+['NewEnd'] :endfun @@ -193,18 +219,10 @@ EOF :$put =string(l) :py l.extend([l[0].name]) :$put =string(l) -:try -: py l[1](1, 2, 3) -:catch -: $put =v:exception[:16] -:endtry +:py ee('l[1](1, 2, 3)') :py f=l[0] :delfunction New -:try -: py f(1, 2, 3) -:catch -: $put =v:exception[:16] -:endtry +:py ee('f(1, 2, 3)') :if has('float') : let l=[0.0] : py l=vim.bindeval('l') @@ -216,7 +234,6 @@ EOF :let messages=[] :delfunction DictNew py < 8 # check if the background thread is working :py del time :py del threading +:py del t :$put =string(l) :" :" settrace @@ -882,29 +900,6 @@ EOF :fun D() :endfun py << EOF -def ee(expr, g=globals(), l=locals()): - try: - exec(expr, g, l) - except: - ei = sys.exc_info() - msg = sys.exc_info()[0].__name__ + ':' + repr(sys.exc_info()[1].args) - msg = msg.replace('TypeError:(\'argument 1 ', 'TypeError:(\'') - if expr.find('None') > -1: - msg = msg.replace('TypeError:(\'iteration over non-sequence\',)', - 'TypeError:("\'NoneType\' object is not iterable",)') - if expr.find('FailingNumber') > -1: - msg = msg.replace(', not \'FailingNumber\'', '').replace('"', '\'') - msg = msg.replace('TypeError:(\'iteration over non-sequence\',)', - 'TypeError:("\'FailingNumber\' object is not iterable",)') - if msg.find('(\'\'') > -1 or msg.find('(\'can\'t') > -1: - msg = msg.replace('(\'', '("').replace('\',)', '",)') - if expr == 'fd(self=[])': - # HACK: PyMapping_Check changed meaning - msg = msg.replace('AttributeError:(\'keys\',)', - 'TypeError:(\'unable to convert list to vim dictionary\',)') - cb.append(expr + ':' + msg) - else: - cb.append(expr + ':NOT FAILED') d = vim.Dictionary() ned = vim.Dictionary(foo='bar', baz='abcD') dl = vim.Dictionary(a=1) @@ -1276,6 +1271,7 @@ ee('Exe("throw \'def\'")') ee('vim.eval("Exe(\'throw \'\'ghi\'\'\')")') ee('vim.eval("Exe(\'echoerr \'\'jkl\'\'\')")') ee('vim.eval("Exe(\'xxx_non_existent_command_xxx\')")') +ee('vim.eval("xxx_unknown_function_xxx()")') ee('vim.bindeval("Exe(\'xxx_non_existent_command_xxx\')")') del Exe EOF diff --git a/src/testdir/test86.ok b/src/testdir/test86.ok --- a/src/testdir/test86.ok +++ b/src/testdir/test86.ok @@ -53,8 +53,8 @@ None [function('New'), function('DictNew'), 'NewStart', 1, 2, 3, 'NewEnd'] [function('New'), function('DictNew'), 'NewStart', 1, 2, 3, 'NewEnd', 'DictNewStart', 1, 2, 3, 'DictNewEnd', {'a': 'b'}] [function('New'), function('DictNew'), 'NewStart', 1, 2, 3, 'NewEnd', 'DictNewStart', 1, 2, 3, 'DictNewEnd', {'a': 'b'}, 'New'] -Vim(python):E725: -Vim(python):E117: +l[1](1, 2, 3):error:('Vim:E725: Calling dict function without Dictionary: DictNew',) +f(1, 2, 3):error:('Vim:E117: Unknown function: New',) [0.0, 0.0] KeyError TypeError @@ -1197,6 +1197,7 @@ Exe("throw 'def'"):error:('def',) vim.eval("Exe('throw ''ghi''')"):error:('ghi',) vim.eval("Exe('echoerr ''jkl''')"):error:('Vim(echoerr):jkl',) vim.eval("Exe('xxx_non_existent_command_xxx')"):error:('Vim:E492: Not an editor command: xxx_non_existent_command_xxx',) +vim.eval("xxx_unknown_function_xxx()"):error:('Vim:E117: Unknown function: xxx_unknown_function_xxx',) vim.bindeval("Exe('xxx_non_existent_command_xxx')"):error:('Vim:E492: Not an editor command: xxx_non_existent_command_xxx',) Caught KeyboardInterrupt Running :put diff --git a/src/testdir/test87.in b/src/testdir/test87.in --- a/src/testdir/test87.in +++ b/src/testdir/test87.in @@ -172,6 +172,36 @@ EOF :unlockvar! l :" :" Function calls +py3 << EOF +import sys +import re + +py33_type_error_pattern = re.compile('^__call__\(\) takes (\d+) positional argument but (\d+) were given$') + +def ee(expr, g=globals(), l=locals()): + cb = vim.current.buffer + try: + try: + exec(expr, g, l) + except Exception as e: + if sys.version_info >= (3, 3) and e.__class__ is AttributeError and str(e).find('has no attribute')>=0 and not str(e).startswith("'vim."): + cb.append(expr + ':' + repr((e.__class__, AttributeError(str(e)[str(e).rfind(" '") + 2:-1])))) + elif sys.version_info >= (3, 3) and e.__class__ is ImportError and str(e).find('No module named \'') >= 0: + cb.append(expr + ':' + repr((e.__class__, ImportError(str(e).replace("'", ''))))) + elif sys.version_info >= (3, 3) and e.__class__ is TypeError: + m = py33_type_error_pattern.search(str(e)) + if m: + msg = '__call__() takes exactly {0} positional argument ({1} given)'.format(m.group(1), m.group(2)) + cb.append(expr + ':' + repr((e.__class__, TypeError(msg)))) + else: + cb.append(expr + ':' + repr((e.__class__, e))) + else: + cb.append(expr + ':' + repr((e.__class__, e))) + else: + cb.append(expr + ':NOT FAILED') + except Exception as e: + cb.append(expr + '::' + repr((e.__class__, e))) +EOF :fun New(...) : return ['NewStart']+a:000+['NewEnd'] :endfun @@ -186,18 +216,10 @@ EOF :$put =string(l) :py3 l+=[l[0].name] :$put =string(l) -:try -: py3 l[1](1, 2, 3) -:catch -: $put =v:exception[:13] -:endtry +:py3 ee('l[1](1, 2, 3)') :py3 f=l[0] :delfunction New -:try -: py3 f(1, 2, 3) -:catch -: $put =v:exception[:13] -:endtry +:py3 ee('f(1, 2, 3)') :if has('float') : let l=[0.0] : py3 l=vim.bindeval('l') @@ -315,6 +337,7 @@ EOF :py3 l[0] = t.t > 8 # check if the background thread is working :py3 del time :py3 del threading +:py3 del t :$put =string(l) :" :" settrace @@ -829,33 +852,6 @@ EOF :fun D() :endfun py3 << EOF -import re - -py33_type_error_pattern = re.compile('^__call__\(\) takes (\d+) positional argument but (\d+) were given$') - -def ee(expr, g=globals(), l=locals()): - try: - try: - exec(expr, g, l) - except Exception as e: - if sys.version_info >= (3, 3) and e.__class__ is AttributeError and str(e).find('has no attribute')>=0 and not str(e).startswith("'vim."): - cb.append(expr + ':' + repr((e.__class__, AttributeError(str(e)[str(e).rfind(" '") + 2:-1])))) - elif sys.version_info >= (3, 3) and e.__class__ is ImportError and str(e).find('No module named \'') >= 0: - cb.append(expr + ':' + repr((e.__class__, ImportError(str(e).replace("'", ''))))) - elif sys.version_info >= (3, 3) and e.__class__ is TypeError: - m = py33_type_error_pattern.search(str(e)) - if m: - msg = '__call__() takes exactly {0} positional argument ({1} given)'.format(m.group(1), m.group(2)) - cb.append(expr + ':' + repr((e.__class__, TypeError(msg)))) - else: - cb.append(expr + ':' + repr((e.__class__, e))) - else: - cb.append(expr + ':' + repr((e.__class__, e))) - else: - cb.append(expr + ':NOT FAILED') - except Exception as e: - cb.append(expr + '::' + repr((e.__class__, e))) - d = vim.Dictionary() ned = vim.Dictionary(foo='bar', baz='abcD') dl = vim.Dictionary(a=1) @@ -1227,6 +1223,7 @@ ee('Exe("throw \'def\'")') ee('vim.eval("Exe(\'throw \'\'ghi\'\'\')")') ee('vim.eval("Exe(\'echoerr \'\'jkl\'\'\')")') ee('vim.eval("Exe(\'xxx_non_existent_command_xxx\')")') +ee('vim.eval("xxx_unknown_function_xxx()")') ee('vim.bindeval("Exe(\'xxx_non_existent_command_xxx\')")') del Exe EOF diff --git a/src/testdir/test87.ok b/src/testdir/test87.ok --- a/src/testdir/test87.ok +++ b/src/testdir/test87.ok @@ -53,8 +53,8 @@ None [function('New'), function('DictNew'), 'NewStart', 1, 2, 3, 'NewEnd'] [function('New'), function('DictNew'), 'NewStart', 1, 2, 3, 'NewEnd', 'DictNewStart', 1, 2, 3, 'DictNewEnd', {'a': 'b'}] [function('New'), function('DictNew'), 'NewStart', 1, 2, 3, 'NewEnd', 'DictNewStart', 1, 2, 3, 'DictNewEnd', {'a': 'b'}, 'New'] -Vim(py3):E725: -Vim(py3):E117: +l[1](1, 2, 3):(, error('Vim:E725: Calling dict function without Dictionary: DictNew',)) +f(1, 2, 3):(, error('Vim:E117: Unknown function: New',)) [0.0, 0.0] KeyError TypeError @@ -1186,6 +1186,7 @@ Exe("throw 'def'"):(, vim.eval("Exe('throw ''ghi''')"):(, error('ghi',)) vim.eval("Exe('echoerr ''jkl''')"):(, error('Vim(echoerr):jkl',)) vim.eval("Exe('xxx_non_existent_command_xxx')"):(, error('Vim:E492: Not an editor command: xxx_non_existent_command_xxx',)) +vim.eval("xxx_unknown_function_xxx()"):(, error('Vim:E117: Unknown function: xxx_unknown_function_xxx',)) vim.bindeval("Exe('xxx_non_existent_command_xxx')"):(, error('Vim:E492: Not an editor command: xxx_non_existent_command_xxx',)) Caught KeyboardInterrupt Running :put diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -739,6 +739,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 107, +/**/ 106, /**/ 105,