patch 8.1.0798: changing a blob while iterating over it works strangely

Problem:    Changing a blob while iterating over it works strangely.
Solution:   Make a copy of the Blob before iterating.
This commit is contained in:
Bram Moolenaar
2019-01-23 21:56:21 +01:00
parent bf821bccf1
commit dd29ea1805
5 changed files with 50 additions and 23 deletions

View File

@ -57,6 +57,28 @@ rettv_blob_set(typval_T *rettv, blob_T *b)
++b->bv_refcount; ++b->bv_refcount;
} }
int
blob_copy(typval_T *from, typval_T *to)
{
int ret = OK;
to->v_type = VAR_BLOB;
if (from->vval.v_blob == NULL)
to->vval.v_blob = NULL;
else if (rettv_blob_alloc(to) == FAIL)
ret = FAIL;
else
{
int len = from->vval.v_blob->bv_ga.ga_len;
if (len > 0)
to->vval.v_blob->bv_ga.ga_data =
vim_memsave(from->vval.v_blob->bv_ga.ga_data, len);
to->vval.v_blob->bv_ga.ga_len = len;
}
return ret;
}
void void
blob_free(blob_T *b) blob_free(blob_T *b)
{ {

View File

@ -2587,7 +2587,6 @@ eval_for_line(
char_u *expr; char_u *expr;
typval_T tv; typval_T tv;
list_T *l; list_T *l;
blob_T *b;
*errp = TRUE; /* default: there is an error */ *errp = TRUE; /* default: there is an error */
@ -2632,16 +2631,17 @@ eval_for_line(
} }
else if (tv.v_type == VAR_BLOB) else if (tv.v_type == VAR_BLOB)
{ {
b = tv.vval.v_blob; fi->fi_bi = 0;
if (b == NULL) if (tv.vval.v_blob != NULL)
clear_tv(&tv);
else
{ {
// No need to increment the refcount, it's already set for typval_T btv;
// the blob being used in "tv".
fi->fi_blob = b; // Make a copy, so that the iteration still works when the
fi->fi_bi = 0; // blob is changed.
blob_copy(&tv, &btv);
fi->fi_blob = btv.vval.v_blob;
} }
clear_tv(&tv);
} }
else else
{ {
@ -8076,7 +8076,7 @@ tv_check_lock(int lock, char_u *name, int use_gettext)
/* /*
* Copy the values from typval_T "from" to typval_T "to". * Copy the values from typval_T "from" to typval_T "to".
* When needed allocates string or increases reference count. * When needed allocates string or increases reference count.
* Does not make a copy of a list or dict but copies the reference! * Does not make a copy of a list, blob or dict but copies the reference!
* It is OK for "from" and "to" to point to the same item. This is used to * It is OK for "from" and "to" to point to the same item. This is used to
* make a copy later. * make a copy later.
*/ */
@ -8216,19 +8216,7 @@ item_copy(
ret = FAIL; ret = FAIL;
break; break;
case VAR_BLOB: case VAR_BLOB:
to->v_type = VAR_BLOB; ret = blob_copy(from, to);
if (from->vval.v_blob == NULL)
to->vval.v_blob = NULL;
else if (rettv_blob_alloc(to) == FAIL)
ret = FAIL;
else
{
int len = from->vval.v_blob->bv_ga.ga_len;
to->vval.v_blob->bv_ga.ga_data =
vim_memsave(from->vval.v_blob->bv_ga.ga_data, len);
to->vval.v_blob->bv_ga.ga_len = len;
}
break; break;
case VAR_DICT: case VAR_DICT:
to->v_type = VAR_DICT; to->v_type = VAR_DICT;

View File

@ -2,6 +2,7 @@
blob_T *blob_alloc(void); blob_T *blob_alloc(void);
int rettv_blob_alloc(typval_T *rettv); int rettv_blob_alloc(typval_T *rettv);
void rettv_blob_set(typval_T *rettv, blob_T *b); void rettv_blob_set(typval_T *rettv, blob_T *b);
int blob_copy(typval_T *from, typval_T *to);
void blob_free(blob_T *b); void blob_free(blob_T *b);
void blob_unref(blob_T *b); void blob_unref(blob_T *b);
long blob_len(blob_T *b); long blob_len(blob_T *b);

View File

@ -154,6 +154,7 @@ func Test_blob_for_loop()
call assert_equal(i, byte) call assert_equal(i, byte)
let i += 1 let i += 1
endfor endfor
call assert_equal(4, i)
let blob = 0z00 let blob = 0z00
call remove(blob, 0) call remove(blob, 0)
@ -161,6 +162,19 @@ func Test_blob_for_loop()
for byte in blob for byte in blob
call assert_error('loop over empty blob') call assert_error('loop over empty blob')
endfor endfor
let blob = 0z0001020304
let i = 0
for byte in blob
call assert_equal(i, byte)
if i == 1
call remove(blob, 0)
elseif i == 3
call remove(blob, 3)
endif
let i += 1
endfor
call assert_equal(5, i)
endfunc endfunc
func Test_blob_concatenate() func Test_blob_concatenate()

View File

@ -791,6 +791,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
798,
/**/ /**/
797, 797,
/**/ /**/