]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.0.2015: Vim9: does not handle islocked() from a method correctly v9.0.2015
authorErnie Rael <errael@raelity.com>
Wed, 11 Oct 2023 19:35:11 +0000 (21:35 +0200)
committerChristian Brabandt <cb@256bit.org>
Wed, 11 Oct 2023 19:38:24 +0000 (21:38 +0200)
Problem:  Vim9: does not handle islocked() from a method correctly
Solution: Handle islocked() builtin from a method.

- Setup `lval_root` from `f_islocked()`.
- Add function `fill_exec_lval_root()` to get info about executing method.
- `sync_root` added in get_lval to handle method member access.
- Conservative approach to reference counting.

closes: #13309

Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Ernie Rael <errael@raelity.com>
src/eval.c
src/evalfunc.c
src/proto/vim9execute.pro
src/structs.h
src/testdir/test_vim9_class.vim
src/version.c
src/vim9execute.c

index 8b26eb189fb0683db9e7a8b82626c2aafa6cbde3..4da6246aced1eed83c3db06eb134c994b1f79583 100644 (file)
@@ -1050,11 +1050,14 @@ flag_string_T glv_flag_strings[] = {
  *     execute_instructions: ISN_LOCKUNLOCK - sets lval_root from stack.
  */
     static void
-get_lval_root(lval_T *lp, lval_root_T *lr)
+fill_lval_from_lval_root(lval_T *lp, lval_root_T *lr)
 {
 #ifdef LOG_LOCKVAR
-    ch_log(NULL, "LKVAR: get_lval_root(): name %s", lp->ll_name);
+    ch_log(NULL, "LKVAR: fill_lval_from_lval_root(): name %s, tv %p",
+                                               lp->ll_name, (void*)lr->lr_tv);
 #endif
+    if (lr->lr_tv == NULL)
+       return;
     if (!lr->lr_is_arg && lr->lr_tv->v_type == VAR_CLASS)
     {
        if (lr->lr_tv->vval.v_class != NULL)
@@ -1177,15 +1180,14 @@ get_lval(
 
 #ifdef LOG_LOCKVAR
     if (lval_root == NULL)
-       ch_log(NULL,
-              "LKVAR: get_lval(): name %s, lval_root (nil)", name);
+       ch_log(NULL, "LKVAR: get_lval(): name: %s, lval_root (nil)", name);
     else
-       ch_log(NULL,
-          "LKVAR: get_lval(): name %s, lr_tv %p lr_is_arg %d",
-           name, (void*)lval_root->lr_tv, lval_root->lr_is_arg);
+       ch_log(NULL, "LKVAR: get_lval(): name: %s, lr_tv %p lr_is_arg %d",
+                       name, (void*)lval_root->lr_tv, lval_root->lr_is_arg);
     char buf[80];
-    ch_log(NULL, "LKVAR:    ...: GLV flags %s",
+    ch_log(NULL, "LKVAR:    ...: GLV flags: %s",
                    flags_tostring(flags, glv_flag_strings, buf, sizeof(buf)));
+    int log_sync_root_key = FALSE;
 #endif
 
     // Clear everything in "lp".
@@ -1324,20 +1326,26 @@ get_lval(
        }
     }
 
-    // Without [idx] or .key we are done.
-    if ((*p != '[' && *p != '.'))
+    int sync_root = FALSE;
+    if (vim9script && lval_root != NULL)
+    {
+       cl_exec = lval_root->lr_cl_exec;
+       sync_root = lval_root->lr_sync_root;
+    }
+
+    // Without [idx] or .key we are done, unless doing sync_root.
+    if (*p != '[' && *p != '.' && (*name == NUL || !sync_root))
     {
        if (lval_root != NULL)
-           get_lval_root(lp, lval_root);
+           fill_lval_from_lval_root(lp, lval_root);
        return p;
     }
 
-    if (vim9script && lval_root != NULL)
+    if (vim9script && lval_root != NULL && lval_root->lr_tv != NULL)
     {
        // using local variable
        lp->ll_tv = lval_root->lr_tv;
        v = NULL;
-       cl_exec = lval_root->lr_cl_exec;
     }
     else
     {
@@ -1367,7 +1375,7 @@ get_lval(
      */
     var1.v_type = VAR_UNKNOWN;
     var2.v_type = VAR_UNKNOWN;
-    while (*p == '[' || (*p == '.' && p[1] != '=' && p[1] != '.'))
+    while (*p == '[' || (*p == '.' && p[1] != '=' && p[1] != '.') || sync_root)
     {
        vartype_T v_type = lp->ll_tv->v_type;
 
@@ -1407,6 +1415,10 @@ get_lval(
                emsg(_(e_slice_must_come_last));
            return NULL;
        }
+#ifdef LOG_LOCKVAR
+       ch_log(NULL, "LKVAR: get_lval() loop: p: %s, type: %s", p,
+                                                       vartype_name(v_type));
+#endif
 
        if (vim9script && lp->ll_valtype == NULL
                && v != NULL
@@ -1417,11 +1429,29 @@ get_lval(
 
            // Vim9 script local variable: get the type
            if (sv != NULL)
+           {
                lp->ll_valtype = sv->sv_type;
+#ifdef LOG_LOCKVAR
+               ch_log(NULL, "LKVAR:    ... loop: vim9 assign type: %s",
+                                       vartype_name(lp->ll_valtype->tt_type));
+#endif
+           }
        }
 
        len = -1;
-       if (*p == '.')
+       if (sync_root)
+       {
+           // For example, the first token is a member variable name and
+           // lp->ll_tv is a class/object.
+           // Process it directly without looking for "[idx]" or ".name".
+           key = name;
+           sync_root = FALSE;  // only first time through
+#ifdef LOG_LOCKVAR
+           log_sync_root_key = TRUE;
+           ch_log(NULL, "LKVAR:    ... loop: name: %s, sync_root", name);
+#endif
+       }
+       else if (*p == '.')
        {
            key = p + 1;
            for (len = 0; ASCII_ISALNUM(key[len]) || key[len] == '_'; ++len)
@@ -1512,6 +1542,17 @@ get_lval(
            // Skip to past ']'.
            ++p;
        }
+#ifdef LOG_LOCKVAR
+       if (log_sync_root_key)
+           ch_log(NULL, "LKVAR:    ... loop: p: %s, sync_root key: %s", p,
+                                                                       key);
+       else if (len == -1)
+           ch_log(NULL, "LKVAR:    ... loop: p: %s, '[' key: %s", p,
+                               empty1 ? ":" : (char*)tv_get_string(&var1));
+       else
+           ch_log(NULL, "LKVAR:    ... loop: p: %s, '.' key: %s", p, key);
+       log_sync_root_key = FALSE;
+#endif
 
        if (v_type == VAR_DICT)
        {
@@ -1700,8 +1741,14 @@ get_lval(
            lp->ll_list = NULL;
 
            class_T *cl;
-           if (v_type == VAR_OBJECT && lp->ll_tv->vval.v_object != NULL)
+           if (v_type == VAR_OBJECT)
            {
+               if (lp->ll_tv->vval.v_object == NULL)
+               {
+                   if (!quiet)
+                       emsg(_(e_using_null_object));
+                   return NULL;
+               }
                cl = lp->ll_tv->vval.v_object->obj_class;
                lp->ll_object = lp->ll_tv->vval.v_object;
            }
index b840220bd0cf49c2b71c9c466025fcf82d237abb..5fccf5270c4a47d37d67245e3f1e66bc3107c375 100644 (file)
@@ -7307,6 +7307,83 @@ f_invert(typval_T *argvars, typval_T *rettv)
     rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL);
 }
 
+/*
+ * Free resources in lval_root allocated by fill_exec_lval_root().
+ */
+    static void
+free_lval_root(lval_root_T *root)
+{
+    if (root->lr_tv != NULL)
+       free_tv(root->lr_tv);
+    class_unref(root->lr_cl_exec);
+    root->lr_tv = NULL;
+    root->lr_cl_exec = NULL;
+}
+
+/*
+ * This is used if executing in a method, the argument string is a
+ * variable/item expr/reference. If it starts with a potential class/object
+ * variable then return OK, may get later errors in get_lval.
+ *
+ * Adjust "root" as needed. Note that name may change (for example to skip
+ * "this") and is returned. lr_tv may be changed or freed.
+ *
+ * Always returns OK.
+ * Free resources and return FAIL if the root should not be used. Otherwise OK.
+ */
+
+    static int
+fix_variable_reference_lval_root(lval_root_T *root, char_u **p_name)
+{
+    char_u     *name = *p_name;
+    char_u     *end;
+    dictitem_T *di;
+
+    // Only set lr_sync_root and lr_tv if the name is an object/class
+    // reference: object ("this.") or class because name is class variable.
+    if (root->lr_tv->v_type == VAR_OBJECT)
+    {
+       if (STRNCMP("this.", name, 5) == 0)
+       {
+           name += 5;
+               root->lr_sync_root = TRUE;
+       }
+       else if (STRCMP("this", name) == 0)
+       {
+           name += 4;
+           root->lr_sync_root = TRUE;
+       }
+    }
+    if (!root->lr_sync_root)   // not object member, try class member
+    {
+       // Explicitly check if the name is a class member.
+       // If it's not then do nothing.
+       for (end = name; ASCII_ISALNUM(*end) || *end == '_'; ++end)
+           ;
+       if (class_member_lookup(root->lr_cl_exec, name, end - name, NULL)
+                                                                   != NULL)
+       {
+           // Using a class, so reference the class tv.
+           di = find_var(root->lr_cl_exec->class_name, NULL, FALSE);
+           if (di != NULL)
+           {
+               // replace the lr_tv
+               clear_tv(root->lr_tv);
+               copy_tv(&di->di_tv, root->lr_tv);
+               root->lr_sync_root = TRUE;
+           }
+       }
+    }
+    if (!root->lr_sync_root)
+    {
+       free_tv(root->lr_tv);
+       root->lr_tv = NULL;         // Not a member variable
+    }
+    *p_name = name;
+    // If FAIL, then must free_lval_root(root);
+    return OK;
+}
+
 /*
  * "islocked()" function
  */
@@ -7322,9 +7399,34 @@ f_islocked(typval_T *argvars, typval_T *rettv)
     if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
        return;
 
-    end = get_lval(tv_get_string(&argvars[0]), NULL, &lv, FALSE, FALSE,
+    char_u *name = tv_get_string(&argvars[0]);
+#ifdef LOG_LOCKVAR
+    ch_log(NULL, "LKVAR: f_islocked(): name: %s", name);
+#endif
+
+    lval_root_T        aroot;  // fully initialized in fill_exec_lval_root
+    lval_root_T *root = NULL;
+
+    // Set up lval_root if executing in a method.
+    if (fill_exec_lval_root(&aroot) == OK)
+    {
+       // Almost always produces a valid lval_root since lr_cl_exec is used
+       // for access verification, lr_tv may be set to NULL.
+       char_u *tname = name;
+       if (fix_variable_reference_lval_root(&aroot, &tname) == OK)
+       {
+           name = tname;
+           root = &aroot;
+       }
+    }
+
+    lval_root_T        *lval_root_save = lval_root;
+    lval_root = root;
+    end = get_lval(name, NULL, &lv, FALSE, FALSE,
                             GLV_NO_AUTOLOAD | GLV_READ_ONLY | GLV_NO_DECL,
                             FNE_CHECK_START);
+    lval_root = lval_root_save;
+
     if (end != NULL && lv.ll_name != NULL)
     {
        if (*end != NUL)
@@ -7347,6 +7449,10 @@ f_islocked(typval_T *argvars, typval_T *rettv)
                                                   || tv_islocked(&di->di_tv));
                }
            }
+           else if (lv.ll_is_root)
+           {
+               rettv->vval.v_number = tv_islocked(lv.ll_tv);
+           }
            else if (lv.ll_object != NULL)
            {
                typval_T *tv = ((typval_T *)(lv.ll_object + 1)) + lv.ll_oi;
@@ -7376,6 +7482,8 @@ f_islocked(typval_T *argvars, typval_T *rettv)
        }
     }
 
+    if (root != NULL)
+       free_lval_root(root);
     clear_lval(&lv);
 }
 
index 7f8e5fd306d18ea1783b02d1b104a4a1da8c5c0c..15beb759fc299a5d6ef4d9a03f8adb24cfea997a 100644 (file)
@@ -4,6 +4,7 @@ void update_has_breakpoint(ufunc_T *ufunc);
 int funcstack_check_refcount(funcstack_T *funcstack);
 int set_ref_in_funcstacks(int copyID);
 int in_def_function(void);
+int fill_exec_lval_root(lval_root_T *lr);
 ectx_T *clear_current_ectx(void);
 void restore_current_ectx(ectx_T *ectx);
 int add_defer_function(char_u *name, int argcount, typval_T *argvars);
index c7cf4128ca091715633835ebe16628fee6a73aaa..51318587419a3028310e289cf5e976b4924b76bd 100644 (file)
@@ -4604,13 +4604,16 @@ typedef struct lval_S
 } lval_T;
 
 /**
- * This may be used to specify the base type that get_lval() uses when
+ * This may be used to specify the base typval that get_lval() uses when
  * following a chain, for example a[idx1][idx2].
+ * The lr_sync_root flags signals get_lval that the first time through
+ * the indexing loop, skip handling  '.' and '[idx]'.
  */
 typedef struct lval_root_S {
     typval_T   *lr_tv;
     class_T    *lr_cl_exec;    // executing class for access checking
     int                lr_is_arg;
+    int                lr_sync_root;
 } lval_root_T;
 
 // Structure used to save the current state.  Used when executing Normal mode
index ce7d5f7faaf1a9305f46ab264c662f51b911bfcc..eead19283a11b56b7095f6b4aacccfb43c1cd115 100644 (file)
@@ -1704,8 +1704,7 @@ def Test_class_member()
     var obj: A
     obj.val = ""
   END
-  # FIXME(in source): this should give E1360 as well!
-  v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected object<A> but got string', 7)
+  v9.CheckSourceFailure(lines, 'E1360: Using a null object', 7)
 
   # Test for accessing a member on a null object, at script level
   lines =<< trim END
@@ -4259,8 +4258,249 @@ def Test_lockvar_islocked()
     assert_equal(0, islocked("C.c1[0]"))
   END
   v9.CheckSourceSuccess(lines)
+
+  # Do islocked() from an object method
+  # and then from a class method
+  lines =<< trim END
+    vim9script
+
+    var l0o0 = [  [0],   [1],   [2]]
+    var l0o1 = [ [10],  [11],  [12]]
+    var l0c0 = [[120], [121], [122]]
+    var l0c1 = [[130], [131], [132]]
+
+    class C0
+      this.o0: list<list<number>> =   l0o0
+      this.o1: list<list<number>> =   l0o1
+      static c0: list<list<number>> = l0c0
+      static c1: list<list<number>> = l0c1
+      def Islocked(arg: string): number
+          return islocked(arg)
+      enddef
+      static def SIslocked(arg: string): number
+        return islocked(arg)
+      enddef
+    endclass
+
+    var l2o0 = [[20000], [20001], [20002]]
+    var l2o1 = [[20010], [20011], [20012]]
+    var l2c0 = [[20120], [20121], [20122]]
+    var l2c1 = [[20130], [20131], [20132]]
+
+    class C2
+      this.o0: list<list<number>> =   l2o0
+      this.o1: list<list<number>> =   l2o1
+      static c0: list<list<number>> = l2c0
+      static c1: list<list<number>> = l2c1
+      def Islocked(arg: string): number
+          return islocked(arg)
+      enddef
+      static def SIslocked(arg: string): number
+        return islocked(arg)
+      enddef
+    endclass
+
+    var obj0 = C0.new()
+    var obj2 = C2.new()
+
+    var l = [ obj0, null_object, obj2 ]
+
+    # lock list, object func access through script var expr
+    assert_equal(0, obj0.Islocked("l[0].o0"))
+    assert_equal(0, obj0.Islocked("l[0].o0[2]"))
+    lockvar l0o0
+    assert_equal(1, obj0.Islocked("l[0].o0"))
+    assert_equal(1, obj0.Islocked("l[0].o0[2]"))
+
+    #echo "check-b" obj2.Islocked("l[1].o1")    # NULL OBJECT
+
+    # lock list element, object func access through script var expr
+    lockvar l0o1[1]
+    assert_equal(0, obj0.Islocked("this.o1[0]"))
+    assert_equal(1, obj0.Islocked("this.o1[1]"))
+
+    assert_equal(0, obj0.Islocked("this.o1"))
+    lockvar l0o1
+    assert_equal(1, obj0.Islocked("this.o1"))
+    unlockvar l0o1
+
+    lockvar l0c1[1]
+
+    # static by class name member expr from same class
+    assert_equal(0, obj0.Islocked("C0.c1[0]"))
+    assert_equal(1, obj0.Islocked("C0.c1[1]"))
+    # static by bare name member expr from same class
+    assert_equal(0, obj0.Islocked("c1[0]"))
+    assert_equal(1, obj0.Islocked("c1[1]"))
+
+    # static by class name member expr from other class
+    assert_equal(0, obj2.Islocked("C0.c1[0]"))
+    assert_equal(1, obj2.Islocked("C0.c1[1]"))
+    # static by bare name member expr from other class
+    assert_equal(0, obj2.Islocked("c1[0]"))
+    assert_equal(0, obj2.Islocked("c1[1]"))
+
+
+    # static by bare name in same class
+    assert_equal(0, obj0.Islocked("c0"))
+    lockvar l0c0
+    assert_equal(1, obj0.Islocked("c0"))
+
+    #
+    # similar stuff, but use static method
+    #
+
+    unlockvar l0o0
+
+    # lock list, object func access through script var expr
+    assert_equal(0, C0.SIslocked("l[0].o0"))
+    assert_equal(0, C0.SIslocked("l[0].o0[2]"))
+    lockvar l0o0
+    assert_equal(1, C0.SIslocked("l[0].o0"))
+    assert_equal(1, C0.SIslocked("l[0].o0[2]"))
+
+    unlockvar l0o1
+
+    # can't access "this" from class method
+    try
+      C0.SIslocked("this.o1[0]")
+      call assert_0(1, '"C0.SIslocked("this.o1[0]")" should have failed')
+    catch
+      call assert_exception('E121: Undefined variable: this')
+    endtry
+
+    lockvar l0c1[1]
+
+    # static by class name member expr from same class
+    assert_equal(0, C0.SIslocked("C0.c1[0]"))
+    assert_equal(1, C0.SIslocked("C0.c1[1]"))
+    # static by bare name member expr from same class
+    assert_equal(0, C0.SIslocked("c1[0]"))
+    assert_equal(1, C0.SIslocked("c1[1]"))
+
+    # static by class name member expr from other class
+    assert_equal(0, C2.SIslocked("C0.c1[0]"))
+    assert_equal(1, C2.SIslocked("C0.c1[1]"))
+    # static by bare name member expr from other class
+    assert_equal(0, C2.SIslocked("c1[0]"))
+    assert_equal(0, C2.SIslocked("c1[1]"))
+
+
+    # static by bare name in same class
+    unlockvar l0c0
+    assert_equal(0, C0.SIslocked("c0"))
+    lockvar l0c0
+    assert_equal(1, C0.SIslocked("c0"))
+  END
+  v9.CheckSourceSuccess(lines)
+
+  # Check islocked class/object from various places.
+  lines =<< trim END
+    vim9script
+
+    class C
+      def Islocked(arg: string): number
+        return islocked(arg)
+      enddef
+      static def SIslocked(arg: string): number
+        return islocked(arg)
+      enddef
+    endclass
+    var obj = C.new()
+
+    # object method
+    assert_equal(0, obj.Islocked("this"))
+    assert_equal(0, obj.Islocked("C"))
+
+    # class method
+    ### assert_equal(0, C.SIslocked("this"))
+    assert_equal(0, C.SIslocked("C"))
+
+    #script level
+    var v: number
+    v = islocked("C")
+    assert_equal(0, v)
+    v = islocked("obj")
+    assert_equal(0, v)
+  END
+  v9.CheckSourceSuccess(lines)
+enddef
+
+def Test_lockvar_islocked_notfound()
+  # Try non-existent things
+  var lines =<< trim END
+    vim9script
+
+    class C
+      def Islocked(arg: string): number
+          return islocked(arg)
+      enddef
+      static def SIslocked(arg: string): number
+        return islocked(arg)
+      enddef
+    endclass
+    var obj = C.new()
+    assert_equal(-1, obj.Islocked("anywhere"))
+    assert_equal(-1, C.SIslocked("notanywhere"))
+  END
+  v9.CheckSourceSuccess(lines)
+
+  # Something not found of the form "name1.name2" is an error
+  lines =<< trim END
+    vim9script
+
+    islocked("one.two")
+  END
+  v9.CheckSourceFailure(lines, 'E121: Undefined variable: one')
+
+  lines =<< trim END
+    vim9script
+
+    class C
+      this.val = { key: "value" }
+      def Islocked(arg: string): number
+          return islocked(arg)
+      enddef
+    endclass
+    var obj = C.new()
+    obj.Islocked("this.val.not_there"))
+  END
+  v9.CheckSourceFailure(lines, 'E716: Key not present in Dictionary: "not_there"')
+
   lines =<< trim END
+    vim9script
+
+    class C
+      def Islocked(arg: string): number
+          return islocked(arg)
+      enddef
+    endclass
+    var obj = C.new()
+    obj.Islocked("this.notobjmember")
   END
+  v9.CheckSourceFailure(lines, 'E1326: Variable not found on object "C": notobjmember')
+
+  # access a script variable through methods
+  lines =<< trim END
+    vim9script
+
+    var l = [1]
+    class C
+      def Islocked(arg: string): number
+          return islocked(arg)
+      enddef
+      static def SIslocked(arg: string): number
+        return islocked(arg)
+      enddef
+    endclass
+    var obj = C.new()
+    assert_equal(0, obj.Islocked("l"))
+    assert_equal(0, C.SIslocked("l"))
+    lockvar l
+    assert_equal(1, obj.Islocked("l"))
+    assert_equal(1, C.SIslocked("l"))
+  END
+  v9.CheckSourceSuccess(lines)
 enddef
 
 " Test for a private object method
index dd64eb36990b85a241750431ea9b7a86128bde26..61f6289e2879c286db1d9ec36f3a98baebedad00 100644 (file)
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    2015,
 /**/
     2014,
 /**/
index 826282241ba785ea7697f872c54cab30d2bd58c3..a89bcd19b4a516c3f5b0eef0aeff217eb486fa86 100644 (file)
@@ -925,6 +925,41 @@ in_def_function(void)
     return current_ectx != NULL;
 }
 
+/*
+ * If executing a class/object method, then fill in the lval_T.
+ * Set lr_tv to the executing item, and lr_exec_class to the executing class;
+ * use free_tv and class_unref when finished with the lval_root.
+ * For use by builtin functions.
+ *
+ * Return FAIL and do nothing if not executing in a class; otherwise OK.
+ */
+    int
+fill_exec_lval_root(lval_root_T *root)
+{
+    ectx_T *ectx = current_ectx;
+    if (ectx != NULL)
+    {
+       dfunc_T     *dfunc = ((dfunc_T *)def_functions.ga_data)
+                                                 + current_ectx->ec_dfunc_idx;
+       ufunc_T    *ufunc = dfunc->df_ufunc;
+       if (ufunc->uf_class != NULL)    // executing a method?
+       {
+           typval_T *tv = alloc_tv();
+           if (tv != NULL)
+           {
+               CLEAR_POINTER(root);
+               root->lr_tv = tv;
+               copy_tv(STACK_TV_VAR(0), root->lr_tv);
+               root->lr_cl_exec = ufunc->uf_class;
+               ++root->lr_cl_exec->class_refcount;
+               return OK;
+           }
+       }
+    }
+
+    return FAIL;
+}
+
 /*
  * Clear "current_ectx" and return the previous value.  To be used when calling
  * a user function.
@@ -4185,21 +4220,20 @@ exec_instructions(ectx_T *ectx)
 
            case ISN_LOCKUNLOCK:
                {
-                   lval_root_T *lval_root_save = lval_root;
-                   int         res;
 #ifdef LOG_LOCKVAR
                    ch_log(NULL, "LKVAR: execute INS_LOCKUNLOCK isn_arg %s",
                                                        iptr->isn_arg.string);
 #endif
+                   lval_root_T *lval_root_save = lval_root;
 
                    // Stack has the local variable, argument the whole :lock
                    // or :unlock command, like ISN_EXEC.
                    --ectx->ec_stack.ga_len;
-                   lval_root_T root = { STACK_TV_BOT(0),
-                                       iptr->isn_arg.lockunlock.lu_cl_exec,
-                                       iptr->isn_arg.lockunlock.lu_is_arg };
+                   lval_root_T root = { .lr_tv = STACK_TV_BOT(0),
+                           .lr_cl_exec = iptr->isn_arg.lockunlock.lu_cl_exec,
+                           .lr_is_arg  = iptr->isn_arg.lockunlock.lu_is_arg };
                    lval_root = &root;
-                   res = exec_command(iptr,
+                   int res = exec_command(iptr,
                                        iptr->isn_arg.lockunlock.lu_string);
                    clear_tv(root.lr_tv);
                    lval_root = lval_root_save;