patch 9.1.0522: Vim9: string(object) hangs for recursive references

Commit: 
https://github.com/vim/vim/commit/05ff4e42fb5aeaf7f7ef7965e44ddfa2d4d2baf3
Author: Ernie Rael <err...@raelity.com>
Date:   Thu Jul 4 16:50:11 2024 +0200

    patch 9.1.0522: Vim9: string(object) hangs for recursive references
    
    Problem:  Vim9: string(object) hangs for recursive references
    Solution: Handle recursive objects specifically, add a hang test and a
              compare test (Ernie Rael)
    
    fixes: #15080
    closes: #15082
    
    Signed-off-by: Ernie Rael <err...@raelity.com>
    Signed-off-by: Yegappan Lakshmanan <yegap...@yahoo.com>
    Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/src/eval.c b/src/eval.c
index 7848feaa8..66c956295 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -6138,6 +6138,58 @@ class_tv2string(typval_T *tv, char_u **tofree)
     return r;
 }
 
+/*
+ * Return a textual representation of an Object in "tv".
+ * If the memory is allocated "tofree" is set to it, otherwise NULL.
+ * When "copyID" is not zero replace recursive object with "...".
+ * When "restore_copyID" is FALSE, repeated items in the object are
+ * replaced with "...".  May return NULL.
+ */
+    static char_u *
+object_tv2string(
+    typval_T   *tv,
+    char_u     **tofree,
+    int                copyID,
+    int                restore_copyID,
+    char_u     *numbuf,
+    int                echo_style,
+    int                composite_val)
+{
+    char_u     *r = NULL;
+
+    object_T   *obj = tv->vval.v_object;
+    if (obj == NULL || obj->obj_class == NULL)
+    {
+       *tofree = NULL;
+       r = (char_u *)"object of [unknown]";
+    }
+    else if (copyID != 0 && obj->obj_copyID == copyID
+            && obj->obj_class->class_obj_member_count != 0)
+    {
+       int n = 25 + strlen((char*)obj->obj_class->class_name);
+       r = alloc(n);
+       if (r != NULL)
+           (void)vim_snprintf((char*)r, n, "object of %s {...}",
+                                               obj->obj_class->class_name);
+       *tofree = r;
+    }
+    else
+    {
+       int old_copyID;
+       if (restore_copyID)
+           old_copyID = obj->obj_copyID;
+
+       obj->obj_copyID = copyID;
+       *tofree = object2string(obj, numbuf, copyID, echo_style,
+                               restore_copyID, composite_val);
+       if (restore_copyID)
+           obj->obj_copyID = old_copyID;
+       r = *tofree;
+    }
+
+    return r;
+}
+
 /*
  * Return a string with the string representation of a variable.
  * If the memory is allocated "tofree" is set to it, otherwise NULL.
@@ -6169,7 +6221,7 @@ echo_string_core(
        {
            // Only give this message once for a recursive call to avoid
            // flooding the user with errors.  And stop iterating over lists
-           // and dicts.
+           // and dicts and objects.
            did_echo_string_emsg = TRUE;
            emsg(_(e_variable_nested_too_deep_for_displaying));
        }
@@ -6227,9 +6279,8 @@ echo_string_core(
            break;
 
        case VAR_OBJECT:
-           *tofree = r = object2string(tv->vval.v_object, numbuf, copyID,
-                                       echo_style, restore_copyID,
-                                       composite_val);
+           r = object_tv2string(tv, tofree, copyID, restore_copyID,
+                                        numbuf, echo_style, composite_val);
            break;
 
        case VAR_FLOAT:
diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim
index f0c27de99..8e328c2ee 100644
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -2246,6 +2246,47 @@ def Test_class_object_to_string()
     assert_equal("object of TextPosition {lnum: 1, col: 22}", string(pos))
   END
   v9.CheckSourceSuccess(lines)
+
+  # check string() with object nesting
+  lines =<< trim END
+    vim9script
+    class C
+        var nest1: C
+        var nest2: C
+        def Init(n1: C, n2: C)
+            this.nest1 = n1
+            this.nest2 = n2
+        enddef
+    endclass
+
+    var o1 = C.new()
+    var o2 = C.new()
+    o1.Init(o1, o2)
+    o2.Init(o2, o1)
+
+    # The following previously put's vim into an infinite loop.
+
+    var expect = "object of C {nest1: object of C {...}, nest2: object of C 
{nest1: object of C {...}, nest2: object of C {...}}}"
+    assert_equal(expect, string(o1))
+  END
+  v9.CheckSourceSuccess(lines)
+
+  lines =<< trim END
+    vim9script
+
+    class B
+    endclass
+
+    class C
+        var b: B
+        var c: C
+    endclass
+
+    var o1 = C.new(B.new(), C.new(B.new()))
+    var expect = "object of C {b: object of B {}, c: object of C {b: object of 
B {}, c: object of [unknown]}}"
+    assert_equal(expect, string(o1))
+  END
+  v9.CheckSourceSuccess(lines)
 enddef
 
 def Test_interface_basics()
@@ -10516,6 +10557,27 @@ def Test_Object_Compare_With_Recursive_Class_Ref()
     assert_equal(false, result)
   END
   v9.CheckScriptSuccess(lines)
+
+  lines =<< trim END
+    vim9script
+    class C
+        var nest1: C
+        var nest2: C
+        def Init(n1: C, n2: C)
+            this.nest1 = n1
+            this.nest2 = n2
+        enddef
+    endclass
+
+    var o1 = C.new()
+    var o2 = C.new()
+    o1.Init(o1, o2)
+    o2.Init(o2, o1)
+
+    var result = o1 == o2
+    assert_equal(true, result)
+  END
+  v9.CheckScriptSuccess(lines)
 enddef
 
 " Test for using a compound operator from a lambda function in an object method
diff --git a/src/version.c b/src/version.c
index 7e4b647a7..249d1c549 100644
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    522,
 /**/
     521,
 /**/
diff --git a/src/vim9class.c b/src/vim9class.c
index cf2af6132..78f5ab8ad 100644
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -3873,7 +3873,9 @@ object_equal(
 }
 
 /*
- * Return a textual representation of object "obj"
+ * Return a textual representation of object "obj".
+ * "obj" must not be NULL.
+ * May return NULL.
  */
     char_u *
 object2string(
@@ -3890,47 +3892,58 @@ object2string(
                                                                == OK
                                        && rettv.vval.v_string != NULL)
        return rettv.vval.v_string;
+
+    int                ok = OK;
+    class_T    *cl = obj->obj_class;
+    garray_T   ga;
+    ga_init2(&ga, 1, 50);
+
+    if (cl != NULL && IS_ENUM(cl))
+    {
+       ga_concat(&ga, (char_u *)"enum ");
+       ga_concat(&ga, cl->class_name);
+       char_u *enum_name = ((typval_T *)(obj + 1))->vval.v_string;
+       ga_concat(&ga, (char_u *)".");
+       ga_concat(&ga, enum_name);
+    }
     else
     {
-       garray_T ga;
-       ga_init2(&ga, 1, 50);
-
-       class_T *cl = obj == NULL ? NULL : obj->obj_class;
-       if (cl != NULL && IS_ENUM(cl))
-       {
-           ga_concat(&ga, (char_u *)"enum ");
-           ga_concat(&ga, cl->class_name);
-           char_u *enum_name = ((typval_T *)(obj + 1))->vval.v_string;
-           ga_concat(&ga, (char_u *)".");
-           ga_concat(&ga, enum_name);
-       }
-       else
-       {
-           ga_concat(&ga, (char_u *)"object of ");
-           ga_concat(&ga, cl == NULL ? (char_u *)"[unknown]"
-                   : cl->class_name);
-       }
-       if (cl != NULL)
-       {
-           ga_concat(&ga, (char_u *)" {");
-           for (int i = 0; i < cl->class_obj_member_count; ++i)
+       ga_concat(&ga, (char_u *)"object of ");
+       ga_concat(&ga, cl == NULL ? (char_u *)"[unknown]"
+               : cl->class_name);
+    }
+    if (cl != NULL)
+    {
+       ga_concat(&ga, (char_u *)" {");
+       for (int i = 0; i < cl->class_obj_member_count; ++i)
+       {
+           if (i > 0)
+               ga_concat(&ga, (char_u *)", ");
+           ocmember_T *m = &cl->class_obj_members[i];
+           ga_concat(&ga, m->ocm_name);
+           ga_concat(&ga, (char_u *)": ");
+           char_u *tf = NULL;
+           char_u *s = echo_string_core((typval_T *)(obj + 1) + i,
+                                        &tf, numbuf, copyID, echo_style,
+                                        restore_copyID, composite_val);
+           if (s != NULL)
+               ga_concat(&ga, s);
+           vim_free(tf);
+           if (s == NULL || did_echo_string_emsg)
            {
-               if (i > 0)
-                   ga_concat(&ga, (char_u *)", ");
-               ocmember_T *m = &cl->class_obj_members[i];
-               ga_concat(&ga, m->ocm_name);
-               ga_concat(&ga, (char_u *)": ");
-               char_u *tf = NULL;
-               ga_concat(&ga, echo_string_core(
-                           (typval_T *)(obj + 1) + i,
-                           &tf, numbuf, copyID, echo_style,
-                           restore_copyID, composite_val));
-               vim_free(tf);
+               ok = FAIL;
+               break;
            }
-           ga_concat(&ga, (char_u *)"}");
+           line_breakcheck();
        }
-       return ga.ga_data;
+       ga_concat(&ga, (char_u *)"}");
+    }
+    if (ok == FAIL)
+    {
+       vim_free(ga.ga_data);
+       return NULL;
     }
+    return (char_u *)ga.ga_data;
 }
 
 /*

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to vim_dev+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/E1sPNwK-002RVQ-C7%40256bit.org.

Raspunde prin e-mail lui