# HG changeset patch # User Bram Moolenaar # Date 1677438003 -3600 # Node ID e8c60d35fce3cdb6c3f45e71341898def9ce888f # Parent 181589e7fc6479c7016ef0572f49ee45ed77d8d4 patch 9.0.1357: using null_object results in an internal error Commit: https://github.com/vim/vim/commit/c4e1b86cb0d88fa5ec1141d3c600e026dcc1bc21 Author: Bram Moolenaar Date: Sun Feb 26 18:58:23 2023 +0000 patch 9.0.1357: using null_object results in an internal error Problem: Using null_object results in an internal error. (Ernie Rael) Solution: Add instructions for pushing an object and class. (closes https://github.com/vim/vim/issues/12044) diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -3449,3 +3449,7 @@ EXTERN char e_using_null_object[] #endif EXTERN char e_cannot_use_color_none_did_you_mean_none[] INIT(= N_("E1361: Cannot use color \"none\", did you mean \"NONE\"?")); +#ifdef FEAT_EVAL +EXTERN char e_cannot_use_non_null_object[] + INIT(= N_("E1362: Cannot use a non-null object")); +#endif diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim --- a/src/testdir/test_vim9_class.vim +++ b/src/testdir/test_vim9_class.vim @@ -186,6 +186,23 @@ def Test_class_defined_twice() source XclassTwice.vim enddef +def Test_returning_null_object() + # this was causing an internal error + var lines =<< trim END + vim9script + + class BufferList + def Current(): any + return null_object + enddef + endclass + + var buffers = BufferList.new() + echo buffers.Current() + END + v9.CheckScriptSuccess(lines) +enddef + def Test_class_interface_wrong_end() var lines =<< trim END vim9script diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -696,6 +696,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1357, +/**/ 1356, /**/ 1355, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -101,6 +101,8 @@ typedef enum { ISN_PUSHFUNC, // push func isn_arg.string ISN_PUSHCHANNEL, // push NULL channel ISN_PUSHJOB, // push NULL job + ISN_PUSHOBJ, // push NULL object + ISN_PUSHCLASS, // push class, uses isn_arg.class ISN_NEWLIST, // push list from stack items, size is isn_arg.number // -1 for null_list ISN_NEWDICT, // push dict from stack items, size is isn_arg.number @@ -518,6 +520,7 @@ struct isn_S { channel_T *channel; job_T *job; partial_T *partial; + class_T *class; jump_T jump; jumparg_T jumparg; forloop_T forloop; diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -1944,8 +1944,7 @@ exec_command(isn_T *iptr) source_cookie_T cookie; SOURCING_LNUM = iptr->isn_lnum; - // Pass getsourceline to get an error for a missing ":end" - // command. + // Pass getsourceline to get an error for a missing ":end" command. CLEAR_FIELD(cookie); cookie.sourcing_lnum = iptr->isn_lnum - 1; if (do_cmdline(iptr->isn_arg.string, @@ -4018,6 +4017,8 @@ exec_instructions(ectx_T *ectx) case ISN_PUSHFUNC: case ISN_PUSHCHANNEL: case ISN_PUSHJOB: + case ISN_PUSHOBJ: + case ISN_PUSHCLASS: if (GA_GROW_FAILS(&ectx->ec_stack, 1)) goto theend; tv = STACK_TV_BOT(0); @@ -4064,6 +4065,14 @@ exec_instructions(ectx_T *ectx) tv->vval.v_job = NULL; #endif break; + case ISN_PUSHOBJ: + tv->v_type = VAR_OBJECT; + tv->vval.v_object = NULL; + break; + case ISN_PUSHCLASS: + tv->v_type = VAR_CLASS; + tv->vval.v_class = iptr->isn_arg.class; + break; default: tv->v_type = VAR_STRING; tv->vval.v_string = iptr->isn_arg.string == NULL @@ -6662,6 +6671,14 @@ list_instructions(char *pfx, isn_T *inst smsg("%s%4d PUSHJOB \"no process\"", pfx, current); #endif break; + case ISN_PUSHOBJ: + smsg("%s%4d PUSHOBJ null", pfx, current); + break; + case ISN_PUSHCLASS: + smsg("%s%4d PUSHCLASS %s", pfx, current, + iptr->isn_arg.class == NULL ? "null" + : (char *)iptr->isn_arg.class->class_name); + break; case ISN_PUSHEXC: smsg("%s%4d PUSH v:exception", pfx, current); break; diff --git a/src/vim9instr.c b/src/vim9instr.c --- a/src/vim9instr.c +++ b/src/vim9instr.c @@ -656,6 +656,35 @@ generate_SETTYPE( } /* + * Generate an ISN_PUSHOBJ instruction. Object is always NULL. + */ + static int +generate_PUSHOBJ(cctx_T *cctx) +{ + RETURN_OK_IF_SKIP(cctx); + if (generate_instr_type(cctx, ISN_PUSHOBJ, &t_any) == NULL) + return FAIL; + return OK; +} + +/* + * Generate an ISN_PUSHCLASS instruction. "class" can be NULL. + */ + static int +generate_PUSHCLASS(cctx_T *cctx, class_T *class) +{ + RETURN_OK_IF_SKIP(cctx); + isn_T *isn = generate_instr_type(cctx, ISN_PUSHCLASS, + class == NULL ? &t_any : &class->class_type); + if (isn == NULL) + return FAIL; + isn->isn_arg.class = class; + if (class != NULL) + ++class->class_refcount; + return OK; +} + +/* * Generate a PUSH instruction for "tv". * "tv" will be consumed or cleared. */ @@ -718,6 +747,17 @@ generate_tv_PUSH(cctx_T *cctx, typval_T generate_PUSHS(cctx, &tv->vval.v_string); tv->vval.v_string = NULL; break; + case VAR_OBJECT: + if (tv->vval.v_object != NULL) + { + emsg(_(e_cannot_use_non_null_object)); + return FAIL; + } + generate_PUSHOBJ(cctx); + break; + case VAR_CLASS: + generate_PUSHCLASS(cctx, tv->vval.v_class); + break; default: siemsg("constant type %d not supported", tv->v_type); clear_tv(tv); @@ -1937,7 +1977,8 @@ generate_PCALL( ret_type = &t_any; else if (type->tt_type == VAR_FUNC || type->tt_type == VAR_PARTIAL) { - if (check_func_args_from_type(cctx, type, argcount, at_top, name) == FAIL) + if (check_func_args_from_type(cctx, type, argcount, at_top, name) + == FAIL) return FAIL; ret_type = type->tt_member; @@ -2467,6 +2508,10 @@ delete_instr(isn_T *isn) blob_unref(isn->isn_arg.blob); break; + case ISN_PUSHCLASS: + class_unref(isn->isn_arg.class); + break; + case ISN_UCALL: vim_free(isn->isn_arg.ufunc.cuf_name); break; @@ -2659,6 +2704,7 @@ delete_instr(isn_T *isn) case ISN_PUSHF: case ISN_PUSHJOB: case ISN_PUSHNR: + case ISN_PUSHOBJ: case ISN_PUSHSPEC: case ISN_PUT: case ISN_REDIREND: