# HG changeset patch # User Bram Moolenaar # Date 1603127704 -7200 # Node ID 6589dae9696ca885fffe441205be849230e9750f # Parent 89aa39d70c6745d07a58d73cb1280278bc879ca7 patch 8.2.1865: Vim9: add() does not check type of argument Commit: https://github.com/vim/vim/commit/1dcae59957301b6b19aef49af648715f911a1378 Author: Bram Moolenaar Date: Mon Oct 19 19:02:42 2020 +0200 patch 8.2.1865: Vim9: add() does not check type of argument Problem: Vim9: add() does not check type of argument. Solution: Inline the add() call. (closes https://github.com/vim/vim/issues/7160) diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -282,4 +282,6 @@ EXTERN char e_endblock_without_block[] INIT(= N_("E1128: } without {")); EXTERN char e_throw_with_empty_string[] INIT(= N_("E1129: Throw with empty string")); +EXTERN char e_cannot_add_to_null_list[] + INIT(= N_("E1130: Cannot add to null list")); #endif diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -273,6 +273,34 @@ def Test_disassemble_list_assign() res) enddef +def s:ListAdd() + var l: list = [] + add(l, 123) + add(l, g:aNumber) +enddef + +def Test_disassemble_list_add() + var res = execute('disass s:ListAdd') + assert_match('\d*_ListAdd\_s*' .. + 'var l: list = []\_s*' .. + '\d NEWLIST size 0\_s*' .. + '\d STORE $0\_s*' .. + 'add(l, 123)\_s*' .. + '\d LOAD $0\_s*' .. + '\d PUSHNR 123\_s*' .. + '\d LISTAPPEND\_s*' .. + '\d DROP\_s*' .. + 'add(l, g:aNumber)\_s*' .. + '\d LOAD $0\_s*' .. + '\d\+ LOADG g:aNumber\_s*' .. + '\d\+ CHECKTYPE number stack\[-1\]\_s*' .. + '\d\+ LISTAPPEND\_s*' .. + '\d\+ DROP\_s*' .. + '\d\+ PUSHNR 0\_s*' .. + '\d\+ RETURN', + res) +enddef + def s:ScriptFuncUnlet() g:somevar = "value" unlet g:somevar @@ -803,7 +831,7 @@ def Test_disassemble_for_loop() 'res->add(i)\_s*' .. '\d LOAD $0\_s*' .. '\d LOAD $2\_s*' .. - '\d\+ BCALL add(argc 2)\_s*' .. + '\d\+ LISTAPPEND\_s*' .. '\d\+ DROP\_s*' .. 'endfor\_s*' .. '\d\+ JUMP -> \d\+\_s*' .. diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -1772,6 +1772,24 @@ def Test_list2str_str2list_utf8() list2str(l, true)->assert_equal(s) enddef +def Test_list_add() + var l: list # defaults to empty list + add(l, 9) + assert_equal([9], l) + + var lines =<< trim END + var l: list + add(l, "x") + END + CheckDefFailure(lines, 'E1012:', 2) + + lines =<< trim END + var l: list = test_null_list() + add(l, 123) + END + CheckDefExecFailure(lines, 'E1130:', 2) +enddef + def SID(): number return expand('') ->matchstr('\zs\d\+\ze_$') diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -751,6 +751,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1865, +/**/ 1864, /**/ 1863, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -96,8 +96,8 @@ typedef enum { ISN_ENDTRY, // take entry off from ec_trystack // more expression operations - ISN_ADDLIST, - ISN_ADDBLOB, + ISN_ADDLIST, // add two lists + ISN_ADDBLOB, // add two blobs // operation with two arguments; isn_arg.op.op_type is exptype_T ISN_OPNR, @@ -120,6 +120,7 @@ typedef enum { ISN_CONCAT, ISN_STRINDEX, // [expr] string index ISN_STRSLICE, // [expr:expr] string slice + ISN_LISTAPPEND, // append to a list, like add() ISN_LISTINDEX, // [expr] list index ISN_LISTSLICE, // [expr:expr] list slice ISN_ANYINDEX, // [expr] runtime index diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1495,6 +1495,32 @@ generate_BCALL(cctx_T *cctx, int func_id } /* + * Generate an ISN_LISTAPPEND instruction. Works like add(). + * Argument count is already checked. + */ + static int +generate_LISTAPPEND(cctx_T *cctx) +{ + garray_T *stack = &cctx->ctx_type_stack; + type_T *list_type; + type_T *item_type; + type_T *expected; + + // Caller already checked that list_type is a list. + list_type = ((type_T **)stack->ga_data)[stack->ga_len - 2]; + item_type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + expected = list_type->tt_member; + if (need_type(item_type, expected, -1, cctx, FALSE, FALSE) == FAIL) + return FAIL; + + if (generate_instr(cctx, ISN_LISTAPPEND) == NULL) + return FAIL; + + --stack->ga_len; // drop the argument + return OK; +} + +/* * Generate an ISN_DCALL or ISN_UCALL instruction. * Return FAIL if the number of arguments is wrong. */ @@ -2537,7 +2563,25 @@ compile_call( // builtin function idx = find_internal_func(name); if (idx >= 0) - res = generate_BCALL(cctx, idx, argcount, argcount_init == 1); + { + if (STRCMP(name, "add") == 0 && argcount == 2) + { + garray_T *stack = &cctx->ctx_type_stack; + type_T *type = ((type_T **)stack->ga_data)[ + stack->ga_len - 2]; + + // TODO: also check for VAR_BLOB + if (type->tt_type == VAR_LIST) + { + // inline "add(list, item)" so that the type can be checked + res = generate_LISTAPPEND(cctx); + idx = -1; + } + } + + if (idx >= 0) + res = generate_BCALL(cctx, idx, argcount, argcount_init == 1); + } else semsg(_(e_unknownfunc), namebuf); goto theend; @@ -7656,6 +7700,7 @@ delete_instr(isn_T *isn) case ISN_FOR: case ISN_GETITEM: case ISN_JUMP: + case ISN_LISTAPPEND: case ISN_LISTINDEX: case ISN_LISTSLICE: case ISN_LOAD: diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -2095,6 +2095,7 @@ call_def_function( || *skipwhite(tv->vval.v_string) == NUL) { vim_free(tv->vval.v_string); + SOURCING_LNUM = iptr->isn_lnum; emsg(_(e_throw_with_empty_string)); goto failed; } @@ -2282,6 +2283,7 @@ call_def_function( typval_T *tv1 = STACK_TV_BOT(-2); typval_T *tv2 = STACK_TV_BOT(-1); + // add two lists or blobs if (iptr->isn_type == ISN_ADDLIST) eval_addlist(tv1, tv2); else @@ -2291,6 +2293,25 @@ call_def_function( } break; + case ISN_LISTAPPEND: + { + typval_T *tv1 = STACK_TV_BOT(-2); + typval_T *tv2 = STACK_TV_BOT(-1); + list_T *l = tv1->vval.v_list; + + // add an item to a list + if (l == NULL) + { + SOURCING_LNUM = iptr->isn_lnum; + emsg(_(e_cannot_add_to_null_list)); + goto on_error; + } + if (list_append_tv(l, tv2) == FAIL) + goto failed; + --ectx.ec_stack.ga_len; + } + break; + // Computation with two arguments of unknown type case ISN_OPANY: { @@ -3410,6 +3431,7 @@ ex_disassemble(exarg_T *eap) case ISN_CONCAT: smsg("%4d CONCAT", current); break; case ISN_STRINDEX: smsg("%4d STRINDEX", current); break; case ISN_STRSLICE: smsg("%4d STRSLICE", current); break; + case ISN_LISTAPPEND: smsg("%4d LISTAPPEND", current); break; case ISN_LISTINDEX: smsg("%4d LISTINDEX", current); break; case ISN_LISTSLICE: smsg("%4d LISTSLICE", current); break; case ISN_ANYINDEX: smsg("%4d ANYINDEX", current); break;