patch 9.0.1955: Vim9: lockvar issues with objects/classes

Problem:  Vim9: lockvar issues with objects/classes
Solution: fix `get_lhs()` object/class access and avoid `SEGV`,
          make error messages more accurate.

- `get_lval()` detects/returns object/class access
- `compile_lock_unlock()` generate code for bare static and obj_arg access
- `do_lock_var()` check lval for `ll_object`/`ll_class` and fail if so.

Details:
- Add `ll_object`/`ll_class`/`ll_oi` to `lval_T`.
- Add `lockunlock_T` to `isn_T` for `is_arg` to specify handling of `lval_root` in `get_lval()`.
- In `get_lval()`, fill in `ll_object`/`ll_class`/`ll_oi` as needed; when no `[idx] or .key`, check lval_root on the way out.
- In `do_lock_var()` check for `ll_object`/`ll_class`; also bullet proof ll_dict case
  and give `Dictionay required` if problem. (not needed to avoid lockvar crash anymore)
- In `compile_lock_unlock()` compile for the class variable and func arg cases.

closes: #13174

Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Ernie Rael <errael@raelity.com>
diff --git a/src/evalvars.c b/src/evalvars.c
index 2438993..fc977c6 100644
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -2123,6 +2123,30 @@
     return FAIL;
 }
 
+    static void
+report_lockvar_member(char *msg, lval_T *lp)
+{
+    int did_alloc = FALSE;
+    char_u *vname = (char_u *)"";
+    char_u *class_name = lp->ll_class != NULL
+				    ? lp->ll_class->class_name : (char_u *)"";
+    if (lp->ll_name != NULL)
+    {
+	if (lp->ll_name_end == NULL)
+	    vname = lp->ll_name;
+	else
+	{
+	    vname = vim_strnsave(lp->ll_name, lp->ll_name_end - lp->ll_name);
+	    if (vname == NULL)
+		return;
+	    did_alloc = TRUE;
+	}
+    }
+    semsg(_(msg), vname, class_name);
+    if (did_alloc)
+	vim_free(vname);
+}
+
 /*
  * Lock or unlock variable indicated by "lp".
  * "deep" is the levels to go (-1 for unlimited);
@@ -2141,6 +2165,10 @@
     int		cc;
     dictitem_T	*di;
 
+#ifdef LOG_LOCKVAR
+    ch_log(NULL, "LKVAR: do_lock_var(): name %s, is_root %d", lp->ll_name, lp->ll_is_root);
+#endif
+
     if (lp->ll_tv == NULL)
     {
 	cc = *name_end;
@@ -2201,10 +2229,13 @@
 	}
 	*name_end = cc;
     }
-    else if (deep == 0)
+    else if (deep == 0 && lp->ll_object == NULL && lp->ll_class == NULL)
     {
 	// nothing to do
     }
+    else if (lp->ll_is_root)
+	// (un)lock the item.
+	item_lock(lp->ll_tv, deep, lock, FALSE);
     else if (lp->ll_range)
     {
 	listitem_T    *li = lp->ll_li;
@@ -2220,13 +2251,57 @@
     else if (lp->ll_list != NULL)
 	// (un)lock a List item.
 	item_lock(&lp->ll_li->li_tv, deep, lock, FALSE);
+    else if (lp->ll_object != NULL)  // This check must be before ll_class.
+    {
+	// (un)lock an object variable.
+	report_lockvar_member(e_cannot_lock_object_variable_str, lp);
+	ret = FAIL;
+    }
+    else if (lp->ll_class != NULL)
+    {
+	// (un)lock a class variable.
+	report_lockvar_member(e_cannot_lock_class_variable_str, lp);
+	ret = FAIL;
+    }
     else
+    {
 	// (un)lock a Dictionary item.
-	item_lock(&lp->ll_di->di_tv, deep, lock, FALSE);
+	if (lp->ll_di == NULL)
+	{
+	    emsg(_(e_dictionary_required));
+	    ret = FAIL;
+	}
+	else
+	    item_lock(&lp->ll_di->di_tv, deep, lock, FALSE);
+    }
 
     return ret;
 }
 
+#ifdef LOG_LOCKVAR
+    static char *
+vartype_tostring(vartype_T vartype)
+{
+    return
+	        vartype == VAR_BOOL ? "v_number"
+	      : vartype == VAR_SPECIAL ? "v_number"
+	      : vartype == VAR_NUMBER ? "v_number"
+	      : vartype == VAR_FLOAT ? "v_float"
+	      : vartype == VAR_STRING ? "v_string"
+	      : vartype == VAR_BLOB ? "v_blob"
+	      : vartype == VAR_FUNC ? "v_string"
+	      : vartype == VAR_PARTIAL ? "v_partial"
+	      : vartype == VAR_LIST ? "v_list"
+	      : vartype == VAR_DICT ? "v_dict"
+	      : vartype == VAR_JOB ? "v_job"
+	      : vartype == VAR_CHANNEL ? "v_channel"
+	      : vartype == VAR_INSTR ? "v_instr"
+	      : vartype == VAR_CLASS ? "v_class"
+	      : vartype == VAR_OBJECT ? "v_object"
+	      : "";
+}
+#endif
+
 /*
  * Lock or unlock an item.  "deep" is nr of levels to go.
  * When "check_refcount" is TRUE do not lock a list or dict with a reference
@@ -2243,6 +2318,10 @@
     hashitem_T	*hi;
     int		todo;
 
+#ifdef LOG_LOCKVAR
+    ch_log(NULL, "LKVAR: item_lock(): type %s", vartype_tostring(tv->v_type));
+#endif
+
     if (recurse >= DICT_MAXNEST)
     {
 	emsg(_(e_variable_nested_too_deep_for_unlock));