From 05ff4e42fb5aeaf7f7ef7965e44ddfa2d4d2baf3 Mon Sep 17 00:00:00 2001 From: Ernie Rael Date: Thu, 4 Jul 2024 16:50:11 +0200 Subject: 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 Signed-off-by: Yegappan Lakshmanan Signed-off-by: Christian Brabandt --- src/eval.c | 59 ++++++++++++++++++++++++++-- src/testdir/test_vim9_class.vim | 62 ++++++++++++++++++++++++++++++ src/version.c | 2 + src/vim9class.c | 85 ++++++++++++++++++++++++----------------- 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 @@ -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 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 @@ -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 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; } /* -- cgit v1.2.3