Tcl Source Code

Check-in [2d215ce103]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Limits on list length were too strict. Revised panics to errors where possible.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 2d215ce10301664f777957ec9bea469373a8fe17
User & Date: dgp 2011-04-21 17:32:20
Context
2011-04-21
18:50
Use macro to set List intreps check-in: 0353d21711 user: dgp tags: trunk
17:32
Limits on list length were too strict. Revised panics to errors where possible. check-in: 2d215ce103 user: dgp tags: trunk
16:53
Limits on list length were too strict. Revised panics to errors where possible. check-in: 9080c06a95 user: dgp tags: core-8-5-branch
13:47
Make sure SetFooFromAny routines react reasonably when passed a NULL interp. check-in: 36b0307ba2 user: dgp tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ChangeLog.

1





2
3
4
5
6
7
8
2011-04-21  Don Porter  <[email protected]>






	* generic/tclCompile.c:	Make sure SetFooFromAny routines react
	* generic/tclIO.c:	reasonably when passed a NULL interp.
	* generic/tclIndexObj.c:
	* generic/tclListObj.c:
	* generic/tclNamesp.c:
	* generic/tclObj.c:

>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
2011-04-21  Don Porter  <[email protected]>

	* generic/tclCmdIL.c:	Limits on list length were too strict.
	* generic/tclInt.h:	Revised panics to errors where possible.
	* generic/tclListObj.c:
	* tests/lrepeat.test:

	* generic/tclCompile.c:	Make sure SetFooFromAny routines react
	* generic/tclIO.c:	reasonably when passed a NULL interp.
	* generic/tclIndexObj.c:
	* generic/tclListObj.c:
	* generic/tclNamesp.c:
	* generic/tclObj.c:

Changes to generic/tclCmdIL.c.

2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591

2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
    /*
     * Skip forward to the interesting arguments now we've finished parsing.
     */

    objc -= 2;
    objv += 2;

    /*
     * Final sanity check. Total number of elements must fit in a signed
     * integer. We also limit the number of elements to 512M-1 so allocations
     * on 32-bit machines are guaranteed to be less than 2GB! [Bug 2130992]
     */

    totalElems = objc * elementCount;
    if (totalElems != 0 && (totalElems/objc != elementCount
	    || totalElems/elementCount != objc)) {
	Tcl_AppendResult(interp, "too many elements in result list", NULL);

        Tcl_SetErrorCode(interp, "TCL", "MEMORY", NULL);
	return TCL_ERROR;
    }
    if (totalElems >= 0x20000000) {
	Tcl_AppendResult(interp, "too many elements in result list", NULL);
        Tcl_SetErrorCode(interp, "TCL", "MEMORY", NULL);
	return TCL_ERROR;
    }

    /*
     * Get an empty list object that is allocated large enough to hold each
     * init value elementCount times.
     */

    listPtr = Tcl_NewListObj(totalElems, NULL);







<
|
<
<
<

<
<
|
|
>



|
<
<
<
<







2575
2576
2577
2578
2579
2580
2581

2582



2583


2584
2585
2586
2587
2588
2589
2590




2591
2592
2593
2594
2595
2596
2597
    /*
     * Skip forward to the interesting arguments now we've finished parsing.
     */

    objc -= 2;
    objv += 2;


    /* Final sanity check. Do not exceed limits on max list length. */






    if (elementCount && objc > LIST_MAX/elementCount) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"max length of a Tcl list (%d elements) exceeded", LIST_MAX));
        Tcl_SetErrorCode(interp, "TCL", "MEMORY", NULL);
	return TCL_ERROR;
    }
    totalElems = objc * elementCount;





    /*
     * Get an empty list object that is allocated large enough to hold each
     * init value elementCount times.
     */

    listPtr = Tcl_NewListObj(totalElems, NULL);

Changes to generic/tclInt.h.

2437
2438
2439
2440
2441
2442
2443



2444
2445
2446
2447
2448
2449
2450
				 * derived from the list representation. May
				 * be ignored if there is no string rep at
				 * all.*/
    Tcl_Obj *elements;		/* First list element; the struct is grown to
				 * accomodate all elements. */
} List;




/*
 * Macro used to get the elements of a list object.
 */

#define ListRepPtr(listPtr) \
    ((List *) (listPtr)->internalRep.twoPtrValue.ptr1)








>
>
>







2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
				 * derived from the list representation. May
				 * be ignored if there is no string rep at
				 * all.*/
    Tcl_Obj *elements;		/* First list element; the struct is grown to
				 * accomodate all elements. */
} List;

#define LIST_MAX \
	(1 + (int)(((size_t)UINT_MAX - sizeof(List))/sizeof(Tcl_Obj *)))

/*
 * Macro used to get the elements of a list object.
 */

#define ListRepPtr(listPtr) \
    ((List *) (listPtr)->internalRep.twoPtrValue.ptr1)

Changes to generic/tclListObj.c.

13
14
15
16
17
18
19


20
21
22
23
24
25
26
27

#include "tclInt.h"

/*
 * Prototypes for functions defined later in this file:
 */



static List *		NewListIntRep(int objc, Tcl_Obj *const objv[]);
static void		DupListInternalRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr);
static void		FreeListInternalRep(Tcl_Obj *listPtr);
static int		SetListFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr);
static void		UpdateStringOfList(Tcl_Obj *listPtr);

/*
 * The structure below defines the list Tcl object type by means of functions







>
>
|







13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

#include "tclInt.h"

/*
 * Prototypes for functions defined later in this file:
 */

static List *		AttemptNewList(Tcl_Interp *interp, int objc,
			    Tcl_Obj *const objv[]);
static List *		NewListIntRep(int objc, Tcl_Obj *const objv[], int p);
static void		DupListInternalRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr);
static void		FreeListInternalRep(Tcl_Obj *listPtr);
static int		SetListFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr);
static void		UpdateStringOfList(Tcl_Obj *listPtr);

/*
 * The structure below defines the list Tcl object type by means of functions
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

74
75
76
77
78
79
80
81
82
83
84
85
86
87
88




89
90
91
92
93




94
95
96
97
98
99
100
};

/*
 *----------------------------------------------------------------------
 *
 * NewListIntRep --
 *
 *	If objc>0 and objv!=NULL, this function creates a list internal rep
 *	with objc elements given in the array objv. If objc>0 and objv==NULL
 *	it creates the list internal rep of a list with 0 elements, where
 *	enough space has been preallocated to store objc elements. If objc<=0,
 *	it returns NULL.
 *
 * Results:
 *	A new List struct is returned. If objc<=0 or if the allocation fails
 *	for lack of memory, NULL is returned. The list returned has refCount
 *	0.
 *
 * Side effects:
 *	The ref counts of the elements in objv are incremented since the
 *	resulting list now refers to them.
 *
 *----------------------------------------------------------------------
 */

static List *
NewListIntRep(
    int objc,
    Tcl_Obj *const objv[])

{
    List *listRepPtr;

    if (objc <= 0) {
	return NULL;
    }

    /*
     * First check to see if we'd overflow and try to allocate an object
     * larger than our memory allocator allows. Note that this is actually a
     * fairly small value when you're on a serious 64-bit machine, but that
     * requires API changes to fix. See [Bug 219196] for a discussion.
     */

    if ((size_t)objc > INT_MAX/sizeof(Tcl_Obj *)) {




	return NULL;
    }

    listRepPtr = attemptckalloc(sizeof(List) + ((objc-1) * sizeof(Tcl_Obj*)));
    if (listRepPtr == NULL) {




	return NULL;
    }

    listRepPtr->canonicalFlag = 0;
    listRepPtr->refCount = 0;
    listRepPtr->maxElemCount = objc;








|
|
|
|
|


|
|
|











|
>




|









|
>
>
>
>





>
>
>
>







47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
};

/*
 *----------------------------------------------------------------------
 *
 * NewListIntRep --
 *
 *	Creates a list internal rep with space for objc elements.  objc
 *	must be > 0.  If objv!=NULL, initializes with the first objc values
 *	in that array.  If objv==NULL, initalize list internal rep to have
 *	0 elements, with space to add objc more.  Flag value "p" indicates
 *	how to behave on failure.
 *
 * Results:
 *	A new List struct with refCount 0 is returned. If some failure
 *	prevents this then if p=0, NULL is returned and otherwise the
 *	routine panics.
 *
 * Side effects:
 *	The ref counts of the elements in objv are incremented since the
 *	resulting list now refers to them.
 *
 *----------------------------------------------------------------------
 */

static List *
NewListIntRep(
    int objc,
    Tcl_Obj *const objv[],
    int p)
{
    List *listRepPtr;

    if (objc <= 0) {
	Tcl_Panic("NewListIntRep: expects postive element count");
    }

    /*
     * First check to see if we'd overflow and try to allocate an object
     * larger than our memory allocator allows. Note that this is actually a
     * fairly small value when you're on a serious 64-bit machine, but that
     * requires API changes to fix. See [Bug 219196] for a discussion.
     */

    if ((size_t)objc > LIST_MAX) {
	if (p) {
	    Tcl_Panic("max length of a Tcl list (%d elements) exceeded",
		    LIST_MAX);
	}
	return NULL;
    }

    listRepPtr = attemptckalloc(sizeof(List) + ((objc-1) * sizeof(Tcl_Obj*)));
    if (listRepPtr == NULL) {
	if (p) {
	    Tcl_Panic("list creation failed: unable to alloc %u bytes",
		    sizeof(List) + ((objc-1) * sizeof(Tcl_Obj *)));
	}
	return NULL;
    }

    listRepPtr->canonicalFlag = 0;
    listRepPtr->refCount = 0;
    listRepPtr->maxElemCount = objc;

109
110
111
112
113
114
115













































116
117
118
119
120
121
122
	    Tcl_IncrRefCount(elemPtrs[i]);
	}
    } else {
	listRepPtr->elemCount = 0;
    }
    return listRepPtr;
}














































/*
 *----------------------------------------------------------------------
 *
 * Tcl_NewListObj --
 *
 *	This function is normally called when not debugging: i.e., when







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
	    Tcl_IncrRefCount(elemPtrs[i]);
	}
    } else {
	listRepPtr->elemCount = 0;
    }
    return listRepPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * AttemptNewList --
 *
 *	Creates a list internal rep with space for objc elements.  objc
 *	must be > 0.  If objv!=NULL, initializes with the first objc values
 *	in that array.  If objv==NULL, initalize list internal rep to have
 *	0 elements, with space to add objc more.  
 *
 * Results:
 *	A new List struct with refCount 0 is returned. If some failure
 *	prevents this then NULL is returned, and an error message is left
 *	in the interp result, unless interp is NULL.
 *
 * Side effects:
 *	The ref counts of the elements in objv are incremented since the
 *	resulting list now refers to them.
 *
 *----------------------------------------------------------------------
 */

static List *
AttemptNewList(
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[])
{
    List *listRepPtr = NewListIntRep(objc, objv, 0);

    if (interp != NULL && listRepPtr == NULL) {
	if (objc > LIST_MAX) {
	    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		    "max length of a Tcl list (%d elements) exceeded",
		    LIST_MAX));
	} else {
	    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		    "list creation failed: unable to alloc %u bytes",
		    sizeof(List) + ((objc-1) * sizeof(Tcl_Obj *))));
	}
	Tcl_SetErrorCode(interp, "TCL", "MEMORY", NULL);
    }
    return listRepPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_NewListObj --
 *
 *	This function is normally called when not debugging: i.e., when
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
	return listPtr;
    }

    /*
     * Create the internal rep.
     */

    listRepPtr = NewListIntRep(objc, objv);
    if (!listRepPtr) {
	Tcl_Panic("Not enough memory to allocate list");
    }

    /*
     * Now create the object.
     */

    Tcl_InvalidateStringRep(listPtr);
    listPtr->internalRep.twoPtrValue.ptr1 = listRepPtr;







|
<
<
<







223
224
225
226
227
228
229
230



231
232
233
234
235
236
237
	return listPtr;
    }

    /*
     * Create the internal rep.
     */

    listRepPtr = NewListIntRep(objc, objv, 1);




    /*
     * Now create the object.
     */

    Tcl_InvalidateStringRep(listPtr);
    listPtr->internalRep.twoPtrValue.ptr1 = listRepPtr;
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
	return listPtr;
    }

    /*
     * Create the internal rep.
     */

    listRepPtr = NewListIntRep(objc, objv);
    if (!listRepPtr) {
	Tcl_Panic("Not enough memory to allocate list");
    }

    /*
     * Now create the object.
     */

    Tcl_InvalidateStringRep(listPtr);
    listPtr->internalRep.twoPtrValue.ptr1 = listRepPtr;







|
<
<
<







292
293
294
295
296
297
298
299



300
301
302
303
304
305
306
	return listPtr;
    }

    /*
     * Create the internal rep.
     */

    listRepPtr = NewListIntRep(objc, objv, 1);




    /*
     * Now create the object.
     */

    Tcl_InvalidateStringRep(listPtr);
    listPtr->internalRep.twoPtrValue.ptr1 = listRepPtr;
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
    /*
     * Set the object's type to "list" and initialize the internal rep.
     * However, if there are no elements to put in the list, just give the
     * object an empty string rep and a NULL type.
     */

    if (objc > 0) {
	listRepPtr = NewListIntRep(objc, objv);
	if (!listRepPtr) {
	    Tcl_Panic("Cannot allocate enough memory for Tcl_SetListObj");
	}
	objPtr->internalRep.twoPtrValue.ptr1 = listRepPtr;
	objPtr->internalRep.twoPtrValue.ptr2 = NULL;
	objPtr->typePtr = &tclListType;
	listRepPtr->refCount++;
    } else {
	objPtr->bytes = tclEmptyStringRep;
	objPtr->length = 0;







|
<
<
<







371
372
373
374
375
376
377
378



379
380
381
382
383
384
385
    /*
     * Set the object's type to "list" and initialize the internal rep.
     * However, if there are no elements to put in the list, just give the
     * object an empty string rep and a NULL type.
     */

    if (objc > 0) {
	listRepPtr = NewListIntRep(objc, objv, 1);



	objPtr->internalRep.twoPtrValue.ptr1 = listRepPtr;
	objPtr->internalRep.twoPtrValue.ptr2 = NULL;
	objPtr->typePtr = &tclListType;
	listRepPtr->refCount++;
    } else {
	objPtr->bytes = tclEmptyStringRep;
	objPtr->length = 0;
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
	newSize = 0;
    }

    if (listRepPtr->refCount > 1) {
	List *oldListRepPtr = listRepPtr;
	Tcl_Obj **oldElems;

	listRepPtr = NewListIntRep(newMax, NULL);
	if (!listRepPtr) {
	    Tcl_Panic("Not enough memory to allocate list");
	}
	oldElems = &oldListRepPtr->elements;
	elemPtrs = &listRepPtr->elements;
	for (i=0; i<numElems; i++) {
	    elemPtrs[i] = oldElems[i];
	    Tcl_IncrRefCount(elemPtrs[i]);
	}







|
|
|







629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
	newSize = 0;
    }

    if (listRepPtr->refCount > 1) {
	List *oldListRepPtr = listRepPtr;
	Tcl_Obj **oldElems;

	listRepPtr = AttemptNewList(interp, newMax, NULL);
	if (listRepPtr == NULL) {
	    return TCL_ERROR;
	}
	oldElems = &oldListRepPtr->elements;
	elemPtrs = &listRepPtr->elements;
	for (i=0; i<numElems; i++) {
	    elemPtrs[i] = oldElems[i];
	    Tcl_IncrRefCount(elemPtrs[i]);
	}
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894

	if (numRequired > listRepPtr->maxElemCount){
	    newMax = 2 * numRequired;
	} else {
	    newMax = listRepPtr->maxElemCount;
	}

	listRepPtr = NewListIntRep(newMax, NULL);
	if (!listRepPtr) {
	    Tcl_Panic("Not enough memory to allocate list");
	}

	listPtr->internalRep.twoPtrValue.ptr1 = listRepPtr;
	listRepPtr->refCount++;

	elemPtrs = &listRepPtr->elements;








|
|
|







925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941

	if (numRequired > listRepPtr->maxElemCount){
	    newMax = 2 * numRequired;
	} else {
	    newMax = listRepPtr->maxElemCount;
	}

	listRepPtr = AttemptNewList(interp, newMax, NULL);
	if (listRepPtr == NULL) {
	    return TCL_ERROR;
	}

	listPtr->internalRep.twoPtrValue.ptr1 = listRepPtr;
	listRepPtr->refCount++;

	elemPtrs = &listRepPtr->elements;

1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
     */

    if (listRepPtr->refCount > 1) {
	List *oldListRepPtr = listRepPtr;
	Tcl_Obj **oldElemPtrs = elemPtrs;
	int i;

	listRepPtr = NewListIntRep(listRepPtr->maxElemCount, NULL);
	if (listRepPtr == NULL) {
	    Tcl_Panic("Not enough memory to allocate list");
	}
	listRepPtr->canonicalFlag = oldListRepPtr->canonicalFlag;
	elemPtrs = &listRepPtr->elements;
	for (i=0; i < elemCount; i++) {
	    elemPtrs[i] = oldElemPtrs[i];
	    Tcl_IncrRefCount(elemPtrs[i]);
	}







|

|







1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
     */

    if (listRepPtr->refCount > 1) {
	List *oldListRepPtr = listRepPtr;
	Tcl_Obj **oldElemPtrs = elemPtrs;
	int i;

	listRepPtr = AttemptNewList(interp, listRepPtr->maxElemCount, NULL);
	if (listRepPtr == NULL) {
	    return TCL_ERROR;
	}
	listRepPtr->canonicalFlag = oldListRepPtr->canonicalFlag;
	elemPtrs = &listRepPtr->elements;
	for (i=0; i < elemCount; i++) {
	    elemPtrs[i] = oldElemPtrs[i];
	    Tcl_IncrRefCount(elemPtrs[i]);
	}
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
	 * the reverse back to a dictionary) are both order-preserving. Also
	 * note that since we know we've got a valid dictionary (by
	 * representation) we also know that fetching the size of the
	 * dictionary or iterating over it will not fail.
	 */

	Tcl_DictObjSize(NULL, objPtr, &size);
	listRepPtr = NewListIntRep(size > 0 ? 2*size : 1, NULL);
	if (!listRepPtr) {
	    if (interp) {
		Tcl_SetResult(interp,
			"insufficient memory to allocate list working space",
			TCL_STATIC);
		Tcl_SetErrorCode(interp, "TCL", "MEMORY", NULL);
	    }
	    return TCL_ERROR;
	}
	listRepPtr->elemCount = 2 * size;

	/*
	 * Populate the list representation.
	 */







|

<
<
<
<
<
<







1759
1760
1761
1762
1763
1764
1765
1766
1767






1768
1769
1770
1771
1772
1773
1774
	 * the reverse back to a dictionary) are both order-preserving. Also
	 * note that since we know we've got a valid dictionary (by
	 * representation) we also know that fetching the size of the
	 * dictionary or iterating over it will not fail.
	 */

	Tcl_DictObjSize(NULL, objPtr, &size);
	listRepPtr = AttemptNewList(interp, size > 0 ? 2*size : 1, NULL);
	if (!listRepPtr) {






	    return TCL_ERROR;
	}
	listRepPtr->elemCount = 2 * size;

	/*
	 * Populate the list representation.
	 */
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
    /*
     * Allocate a new List structure with enough room for "estCount" elements.
     * Each element is a pointer to a Tcl_Obj with the appropriate string rep.
     * The initial "estCount" elements are set using the corresponding "argv"
     * strings.
     */

    listRepPtr = NewListIntRep(estCount, NULL);
    if (!listRepPtr) {
	if (interp) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "Not enough memory to allocate the list internal rep", -1));
	    Tcl_SetErrorCode(interp, "TCL", "MEMORY", NULL);
	}
	return TCL_ERROR;
    }
    elemPtrs = &listRepPtr->elements;

    for (p=string, lenRemain=length, i=0;
	    lenRemain > 0;
	    p=nextElem, lenRemain=limit-nextElem, i++) {







|
|
<
<
<
<
<







1816
1817
1818
1819
1820
1821
1822
1823
1824





1825
1826
1827
1828
1829
1830
1831
    /*
     * Allocate a new List structure with enough room for "estCount" elements.
     * Each element is a pointer to a Tcl_Obj with the appropriate string rep.
     * The initial "estCount" elements are set using the corresponding "argv"
     * strings.
     */

    listRepPtr = AttemptNewList(interp, estCount, NULL);
    if (listRepPtr == NULL) {





	return TCL_ERROR;
    }
    elemPtrs = &listRepPtr->elements;

    for (p=string, lenRemain=length, i=0;
	    lenRemain > 0;
	    p=nextElem, lenRemain=limit-nextElem, i++) {

Changes to tests/lrepeat.test.

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
    -body {
	lrepeat 0 a b c
    }
    -result {}
}
test lrepeat-1.8 {Do not build enormous lists - Bug 2130992} -body {
     lrepeat 0x10000000 a b c d e f g h
} -returnCodes error -result {too many elements in result list}

## Okay
test lrepeat-2.1 {normal cases} {
    lrepeat 10 a
} {a a a a a a a a a a}
test lrepeat-2.2 {normal cases} {
    lrepeat 3 [lrepeat 3 0]







|







59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
    -body {
	lrepeat 0 a b c
    }
    -result {}
}
test lrepeat-1.8 {Do not build enormous lists - Bug 2130992} -body {
     lrepeat 0x10000000 a b c d e f g h
} -returnCodes error -match glob -result *

## Okay
test lrepeat-2.1 {normal cases} {
    lrepeat 10 a
} {a a a a a a a a a a}
test lrepeat-2.2 {normal cases} {
    lrepeat 3 [lrepeat 3 0]