summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorErnie Rael <errael@raelity.com>2024-07-04 16:50:11 +0200
committerChristian Brabandt <cb@256bit.org>2024-07-04 16:50:11 +0200
commit05ff4e42fb5aeaf7f7ef7965e44ddfa2d4d2baf3 (patch)
tree802d7bb1979be627a6764eccb4bc9b0286d1b92b
parent4179f193ccc26e95fec66273ffa036be1ab85e0a (diff)
patch 9.1.0522: Vim9: string(object) hangs for recursive referencesv9.1.0522
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 <errael@raelity.com> Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
-rw-r--r--src/eval.c59
-rw-r--r--src/testdir/test_vim9_class.vim62
-rw-r--r--src/version.c2
-rw-r--r--src/vim9class.c85
4 files changed, 168 insertions, 40 deletions
diff --git a/src/eval.c b/src/eval.c
index 7848feaa87..66c9562953 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -6139,6 +6139,58 @@ class_tv2string(typval_T *tv, char_u **tofree)
}
/*
+ * 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.
* "numbuf" is used for a number.
@@ -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 f0c27de99b..8e328c2ee8 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 7e4b647a76..249d1c5493 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 522,
+/**/
521,
/**/
520,
diff --git a/src/vim9class.c b/src/vim9class.c
index cf2af61325..78f5ab8ada 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;
}
/*