# HG changeset patch # User Bram Moolenaar # Date 1289418117 -3600 # Node ID fcb916bed51a5ae096a9f5f5f697a909b2062040 # Parent 7002404b17b15ea37887cb1dd5b5b95883cdd27f updated for version 7.3.055 Problem: Recursively nested lists and dictionaries cause a near-endless loop when comparing them with a copy. (ZyX) Solution: Limit recursiveness in a way that non-recursive structures can still be nested very deep. Files: src/eval.c, src/testdir/test55.in, src/testdir/test55.ok diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -434,9 +434,9 @@ static listitem_T *listitem_alloc __ARGS static void listitem_free __ARGS((listitem_T *item)); static void listitem_remove __ARGS((list_T *l, listitem_T *item)); static long list_len __ARGS((list_T *l)); -static int list_equal __ARGS((list_T *l1, list_T *l2, int ic)); -static int dict_equal __ARGS((dict_T *d1, dict_T *d2, int ic)); -static int tv_equal __ARGS((typval_T *tv1, typval_T *tv2, int ic)); +static int list_equal __ARGS((list_T *l1, list_T *l2, int ic, int recursive)); +static int dict_equal __ARGS((dict_T *d1, dict_T *d2, int ic, int recursive)); +static int tv_equal __ARGS((typval_T *tv1, typval_T *tv2, int ic, int recursive)); static listitem_T *list_find __ARGS((list_T *l, long n)); static long list_find_nr __ARGS((list_T *l, long idx, int *errorp)); static long list_idx_of_item __ARGS((list_T *l, listitem_T *item)); @@ -4350,7 +4350,8 @@ eval4(arg, rettv, evaluate) else { /* Compare two Lists for being equal or unequal. */ - n1 = list_equal(rettv->vval.v_list, var2.vval.v_list, ic); + n1 = list_equal(rettv->vval.v_list, var2.vval.v_list, + ic, FALSE); if (type == TYPE_NEQUAL) n1 = !n1; } @@ -4379,7 +4380,8 @@ eval4(arg, rettv, evaluate) else { /* Compare two Dictionaries for being equal or unequal. */ - n1 = dict_equal(rettv->vval.v_dict, var2.vval.v_dict, ic); + n1 = dict_equal(rettv->vval.v_dict, var2.vval.v_dict, + ic, FALSE); if (type == TYPE_NEQUAL) n1 = !n1; } @@ -5914,10 +5916,11 @@ list_len(l) * Return TRUE when two lists have exactly the same values. */ static int -list_equal(l1, l2, ic) +list_equal(l1, l2, ic, recursive) list_T *l1; list_T *l2; int ic; /* ignore case for strings */ + int recursive; /* TRUE when used recursively */ { listitem_T *item1, *item2; @@ -5931,7 +5934,7 @@ list_equal(l1, l2, ic) for (item1 = l1->lv_first, item2 = l2->lv_first; item1 != NULL && item2 != NULL; item1 = item1->li_next, item2 = item2->li_next) - if (!tv_equal(&item1->li_tv, &item2->li_tv, ic)) + if (!tv_equal(&item1->li_tv, &item2->li_tv, ic, recursive)) return FALSE; return item1 == NULL && item2 == NULL; } @@ -5953,10 +5956,11 @@ dict_lookup(hi) * Return TRUE when two dictionaries have exactly the same key/values. */ static int -dict_equal(d1, d2, ic) +dict_equal(d1, d2, ic, recursive) dict_T *d1; dict_T *d2; int ic; /* ignore case for strings */ + int recursive; /* TRUE when used recursively */ { hashitem_T *hi; dictitem_T *item2; @@ -5977,7 +5981,7 @@ dict_equal(d1, d2, ic) item2 = dict_find(d2, hi->hi_key, -1); if (item2 == NULL) return FALSE; - if (!tv_equal(&HI2DI(hi)->di_tv, &item2->di_tv, ic)) + if (!tv_equal(&HI2DI(hi)->di_tv, &item2->di_tv, ic, recursive)) return FALSE; --todo; } @@ -5985,41 +5989,54 @@ dict_equal(d1, d2, ic) return TRUE; } +static int tv_equal_recurse_limit; + /* * Return TRUE if "tv1" and "tv2" have the same value. * Compares the items just like "==" would compare them, but strings and * numbers are different. Floats and numbers are also different. */ static int -tv_equal(tv1, tv2, ic) +tv_equal(tv1, tv2, ic, recursive) typval_T *tv1; typval_T *tv2; - int ic; /* ignore case */ + int ic; /* ignore case */ + int recursive; /* TRUE when used recursively */ { char_u buf1[NUMBUFLEN], buf2[NUMBUFLEN]; char_u *s1, *s2; - static int recursive = 0; /* cach recursive loops */ + static int recursive_cnt = 0; /* catch recursive loops */ int r; if (tv1->v_type != tv2->v_type) return FALSE; + /* Catch lists and dicts that have an endless loop by limiting - * recursiveness to 1000. We guess they are equal then. */ - if (recursive >= 1000) + * recursiveness to a limit. We guess they are equal then. + * A fixed limit has the problem of still taking an awful long time. + * Reduce the limit every time running into it. That should work fine for + * deeply linked structures that are not recursively linked and catch + * recursiveness quickly. */ + if (!recursive) + tv_equal_recurse_limit = 1000; + if (recursive_cnt >= tv_equal_recurse_limit) + { + --tv_equal_recurse_limit; return TRUE; + } switch (tv1->v_type) { case VAR_LIST: - ++recursive; - r = list_equal(tv1->vval.v_list, tv2->vval.v_list, ic); - --recursive; + ++recursive_cnt; + r = list_equal(tv1->vval.v_list, tv2->vval.v_list, ic, TRUE); + --recursive_cnt; return r; case VAR_DICT: - ++recursive; - r = dict_equal(tv1->vval.v_dict, tv2->vval.v_dict, ic); - --recursive; + ++recursive_cnt; + r = dict_equal(tv1->vval.v_dict, tv2->vval.v_dict, ic, TRUE); + --recursive_cnt; return r; case VAR_FUNC: @@ -9391,7 +9408,7 @@ f_count(argvars, rettv) } for ( ; li != NULL; li = li->li_next) - if (tv_equal(&li->li_tv, &argvars[1], ic)) + if (tv_equal(&li->li_tv, &argvars[1], ic, FALSE)) ++n; } } @@ -9418,7 +9435,7 @@ f_count(argvars, rettv) if (!HASHITEM_EMPTY(hi)) { --todo; - if (tv_equal(&HI2DI(hi)->di_tv, &argvars[1], ic)) + if (tv_equal(&HI2DI(hi)->di_tv, &argvars[1], ic, FALSE)) ++n; } } @@ -12574,7 +12591,7 @@ f_index(argvars, rettv) } for ( ; item != NULL; item = item->li_next, ++idx) - if (tv_equal(&item->li_tv, &argvars[1], ic)) + if (tv_equal(&item->li_tv, &argvars[1], ic, FALSE)) { rettv->vval.v_number = idx; break; diff --git a/src/testdir/test55.in b/src/testdir/test55.in --- a/src/testdir/test55.in +++ b/src/testdir/test55.in @@ -342,7 +342,18 @@ let l = [0, 1, 2, 3] :$put =(d == d) :$put =(l != deepcopy(l)) :$put =(d != deepcopy(d)) +:" +:" compare complex recursively linked list and dict +:let l = [] +:call add(l, l) +:let dict4 = {"l": l} +:call add(dict4.l, dict4) +:let lcopy = deepcopy(l) +:let dict4copy = deepcopy(dict4) +:$put =(l == lcopy) +:$put =(dict4 == dict4copy) :endfun +:" :call Test(1, 2, [3, 4], {5: 6}) " This may take a while :" :delfunc Test diff --git a/src/testdir/test55.ok b/src/testdir/test55.ok --- a/src/testdir/test55.ok +++ b/src/testdir/test55.ok @@ -109,3 +109,5 @@ 1 1 0 0 +1 +1 diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -715,6 +715,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 55, +/**/ 54, /**/ 53,