summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--generic/tclListObj.c113
1 files changed, 78 insertions, 35 deletions
diff --git a/generic/tclListObj.c b/generic/tclListObj.c
index dc08f4d..8f8d0b9 100644
--- a/generic/tclListObj.c
+++ b/generic/tclListObj.c
@@ -1412,16 +1412,18 @@ TclListObjCopy(
* None.
*
* Side effects:
- * The ListStore and ListSpan referenced by in the returned ListRep
- * may or may not be the same as those passed in. For example, the
- * ListStore may differ because the range is small enough that a new
- * ListStore is more memory-optimal. The ListSpan may differ because
- * it is NULL or shared. Regardless, reference counts on the returned
- * values are not incremented. Generally, ListObjReplaceRepAndInvalidate may be
- * used to store the new ListRep back into an object or a ListRepIncRefs
- * followed by ListRepDecrRefs to free in case of errors.
- * TODO WARNING:- this is not a very clean interface and easy for caller
- * to get wrong. Better change it to pass in the source ListObj
+ * The ListStore and ListSpan referenced by in the returned ListRep
+ * may or may not be the same as those passed in. For example, the
+ * ListStore may differ because the range is small enough that a new
+ * ListStore is more memory-optimal. The ListSpan may differ because
+ * it is NULL or shared. Regardless, reference counts on the returned
+ * values are not incremented. Generally, ListObjReplaceRepAndInvalidate
+ * may be used to store the new ListRep back into an object or a
+ * ListRepIncrRefs followed by ListRepDecrRefs to free in case of errors.
+ * Any other use should be carefully reconsidered.
+ * TODO WARNING:- this is an awkward interface and easy for caller
+ * to get wrong. Mostly due to refcount combinations. Perhaps passing
+ * in the source listObj instead of source listRep might simplify.
*
*------------------------------------------------------------------------
*/
@@ -1438,13 +1440,14 @@ ListRepRange(
Tcl_Obj **srcElems;
ListSizeT numSrcElems = ListRepLength(srcRepPtr);
ListSizeT rangeLen;
- int doSpan;
+ ListSizeT numAfterRangeEnd;
LISTREP_CHECK(srcRepPtr);
/* Take the opportunity to garbage collect */
/* TODO - we probably do not need the preserveSrcRep here unlike later */
if (!preserveSrcRep) {
+ /* T:listrep-1.{4,5,8,9} */
ListRepFreeUnreferenced(srcRepPtr);
}
@@ -1463,11 +1466,13 @@ ListRepRange(
rangeLen = rangeEnd - rangeStart + 1;
/*
- * We can create a range one of three ways:
- * (1) Use a ListSpan referencing the current ListStore
- * (2) Creating a new ListStore
- * (3) Removing all elements outside the range in the current ListStore
- * Option (3) may only be done if caller has not disallowed it AND
+ * We can create a range one of four ways:
+ * (0) Range encapsulates entire list
+ * (1) Special case: deleting in-place from end of an unshared object
+ * (2) Use a ListSpan referencing the current ListStore
+ * (3) Creating a new ListStore
+ * (4) Removing all elements outside the range in the current ListStore
+ * Option (4) may only be done if caller has not disallowed it AND
* the ListStore is not shared.
*
* The choice depends on heuristics related to speed and memory.
@@ -1477,12 +1482,32 @@ ListRepRange(
* string-canonizing effect of [lrange 0 end] so the Tcl_Obj should not
* be returned as is even if the range encompasses the whole list.
*/
- doSpan = ListSpanMerited(rangeLen,
- srcRepPtr->storePtr->numUsed,
- srcRepPtr->storePtr->numAllocated);
-
- if (doSpan) {
- /* Option 1 - because span would be most efficient */
+ if (rangeStart == 0 && rangeEnd == (numSrcElems-1)) {
+ /* Option 0 - entire list. This may be used to canonicalize */
+ *rangeRepPtr = *srcRepPtr; /* Not ref counts not incremented */
+ } else if (rangeStart == 0 && (!preserveSrcRep)
+ && (!ListRepIsShared(srcRepPtr) && srcRepPtr->spanPtr == NULL)) {
+ /* Option 1 - Special case unshared, exclude end elements, no span */
+ LIST_ASSERT(srcRepPtr->storePtr->firstUsed == 0); /* If no span */
+ ListRepElements(srcRepPtr, numSrcElems, srcElems);
+ numAfterRangeEnd = numSrcElems - (rangeEnd + 1);
+ /* Assert: Because numSrcElems > rangeEnd earlier */
+ LIST_ASSERT(numAfterRangeEnd >= 0);
+ if (numAfterRangeEnd != 0) {
+ /* T:listrep-1.{8,9} */
+ ObjArrayDecrRefs(srcElems, rangeEnd + 1, numAfterRangeEnd);
+ }
+ /* srcRepPtr->storePtr->firstUsed,numAllocated unchanged */
+ srcRepPtr->storePtr->numUsed = rangeLen;
+ srcRepPtr->storePtr->flags = 0;
+ rangeRepPtr->storePtr = srcRepPtr->storePtr; /* Note no incr ref */
+ rangeRepPtr->spanPtr = NULL;
+ } else if (ListSpanMerited(rangeLen,
+ srcRepPtr->storePtr->numUsed,
+ srcRepPtr->storePtr->numAllocated)) {
+ /*
+ * Option 2 - because span would be most efficient
+ */
ListSizeT spanStart = ListRepStart(srcRepPtr) + rangeStart;
if (!preserveSrcRep && srcRepPtr->spanPtr
&& srcRepPtr->spanPtr->refCount <= 1) {
@@ -1491,7 +1516,7 @@ ListRepRange(
srcRepPtr->spanPtr->spanLength = rangeLen;
*rangeRepPtr = *srcRepPtr;
} else {
- /* Span not present or is shared - Allocate a new span */
+ /* Span not present or is shared. T:listrep-1.5 */
rangeRepPtr->storePtr = srcRepPtr->storePtr;
rangeRepPtr->spanPtr = ListSpanNew(spanStart, rangeLen);
}
@@ -1505,7 +1530,7 @@ ListRepRange(
ListRepFreeUnreferenced(rangeRepPtr);
}
} else if (preserveSrcRep || ListRepIsShared(srcRepPtr)) {
- /* Option 2 - span or modification in place not allowed/desired */
+ /* Option 3 - span or modification in place not allowed/desired */
ListRepElements(srcRepPtr, numSrcElems, srcElems);
/* TODO - allocate extra space? */
ListRepInit(rangeLen,
@@ -1514,14 +1539,13 @@ ListRepRange(
rangeRepPtr);
} else {
/*
- * Option 3 - modify in place. Note that because of the invariant
+ * Option 4 - modify in place. Note that because of the invariant
* that spanless list stores must start at 0, we have to move
* everything to the front.
* TODO - perhaps if a span already exists, no need to move to front?
* or maybe no need to move all the way to the front?
* TODO - if range is small relative to allocation, allocate new?
*/
- ListSizeT numAfterRangeEnd;
/* Asserts follow from call to ListRepFreeUnreferenced earlier */
LIST_ASSERT(!preserveSrcRep);
@@ -1533,12 +1557,13 @@ ListRepRange(
/* Free leading elements outside range */
if (rangeStart != 0) {
+ /* T:listrep-1.4 */
ObjArrayDecrRefs(srcElems, 0, rangeStart);
}
/* Ditto for trailing */
numAfterRangeEnd = numSrcElems - (rangeEnd + 1);
- LIST_ASSERT(numAfterRangeEnd
- >= 0); /* Because numSrcElems > rangeEnd earlier */
+ /* Assert: Because numSrcElems > rangeEnd earlier */
+ LIST_ASSERT(numAfterRangeEnd >= 0);
if (numAfterRangeEnd != 0) {
ObjArrayDecrRefs(srcElems, rangeEnd + 1, numAfterRangeEnd);
}
@@ -1549,8 +1574,11 @@ ListRepRange(
srcRepPtr->storePtr->firstUsed = 0;
srcRepPtr->storePtr->numUsed = rangeLen;
srcRepPtr->storePtr->flags = 0;
- rangeRepPtr->storePtr = srcRepPtr->storePtr; /* Note no incr ref */
- rangeRepPtr->spanPtr = NULL;
+ if (srcRepPtr->spanPtr) {
+ ListSpanDecrRefs(srcRepPtr->spanPtr);
+ srcRepPtr->spanPtr = NULL;
+ }
+ *rangeRepPtr = *srcRepPtr; /* Note no ref count increments */
}
/* TODO - call freezombies here if !preserveSrcRep? */
@@ -1766,6 +1794,7 @@ Tcl_ListObjAppendList(
LIST_ASSERT(toLen == listRep.storePtr->numUsed);
if (finalLen > listRep.storePtr->numAllocated) {
+ /* T:listrep-1.{2,11} */
ListStore *newStorePtr;
newStorePtr = ListStoreReallocate(listRep.storePtr, finalLen);
if (newStorePtr == NULL) {
@@ -2100,17 +2129,20 @@ Tcl_ListObjReplace(
if (numToInsert == 0) {
if (numToDelete == 0) {
/* Should force canonical even for no-op */
+ /* T:listrep-1.10 */
TclInvalidateStringRep(listObj);
return TCL_OK;
}
if (first == 0) {
- /* Delete from front, so return tail */
+ /* Delete from front, so return tail. */
+ /* T:listrep-1.{4,5} */
ListRep tailRep;
ListRepRange(&listRep, numToDelete, origListLen-1, 0, &tailRep);
ListObjReplaceRepAndInvalidate(listObj, &tailRep);
return TCL_OK;
} else if ((first+numToDelete) >= origListLen) {
/* Delete from tail, so return head */
+ /* T:listrep-1.{8,9} */
ListRep headRep;
ListRepRange(&listRep, 0, first-1, 0, &headRep);
ListObjReplaceRepAndInvalidate(listObj, &headRep);
@@ -2126,8 +2158,9 @@ Tcl_ListObjReplace(
* Check Case (2) - pure inserts under certain conditions:
*/
if (numToDelete == 0) {
- /* Case (2a) - Append to list */
+ /* Case (2a) - Append to list. */
if (first == origListLen) {
+ /* T:listrep-1.11 */
return TclListObjAppendElements(
interp, listObj, numToInsert, insertObjs);
}
@@ -2170,7 +2203,6 @@ Tcl_ListObjReplace(
}
}
-
/* Just for readability of the code */
lenChange = numToInsert - numToDelete;
leadSegmentLen = first;
@@ -2184,6 +2216,7 @@ Tcl_ListObjReplace(
* ListObjReplaceAndInvalidate below.
*/
if (numFreeSlots < lenChange && !ListRepIsShared(&listRep)) {
+ /* T:listrep-1.{1,3} */
ListStore *newStorePtr =
ListStoreReallocate(listRep.storePtr, origListLen + lenChange);
if (newStorePtr == NULL) {
@@ -2264,13 +2297,14 @@ Tcl_ListObjReplace(
/*
* Free up elements to be deleted. Before that, increment the ref counts
- * for objects to be inserted in case there is overlap. See bug3598580
- * or test listobj-11.1
+ * for objects to be inserted in case there is overlap. T:listobj-11.1
*/
if (numToInsert) {
+ /* T:listrep-1.{1,3} */
ObjArrayIncrRefs(insertObjs, 0, numToInsert);
}
if (numToDelete) {
+ /* T:listrep-1.{6,7} */
ObjArrayDecrRefs(listObjs, first, numToDelete);
}
@@ -2295,10 +2329,12 @@ Tcl_ListObjReplace(
*/
if (leadSegmentLen > tailSegmentLen) {
/* Tail segment smaller. Insert after lead, move tail down */
+ /* T:listrep-1.7 */
leadShift = 0;
tailShift = lenChange;
} else {
/* Lead segment smaller. Insert before tail, move lead up */
+ /* T:listrep-1.6 */
leadShift = -lenChange;
tailShift = 0;
}
@@ -2347,6 +2383,7 @@ Tcl_ListObjReplace(
if (finalFreeSpace > 1 && (leadSpace == 0 || leadSegmentLen == 0)) {
ListSizeT postShiftTailSpace = tailSpace - lenChange;
if (postShiftTailSpace > (finalFreeSpace/2)) {
+ /* T:listrep-1.{1,3} */
ListSizeT extraShift = postShiftTailSpace - (finalFreeSpace / 2);
tailShift += extraShift;
leadShift = extraShift; /* Move head to the back as well */
@@ -2390,12 +2427,14 @@ Tcl_ListObjReplace(
if (leadShift > 0) {
/* Will happen when we have to make room at bottom */
if (tailShift != 0 && tailSegmentLen != 0) {
+ /* T:listrep-1.{1,3} */
ListSizeT tailStart = leadSegmentLen + numToDelete;
memmove(&listObjs[tailStart + tailShift],
&listObjs[tailStart],
tailSegmentLen * sizeof(Tcl_Obj *));
}
if (leadSegmentLen != 0) {
+ /* T:listrep-1.{3,6} */
memmove(&listObjs[leadShift],
&listObjs[0],
leadSegmentLen * sizeof(Tcl_Obj *));
@@ -2407,6 +2446,7 @@ Tcl_ListObjReplace(
leadSegmentLen * sizeof(Tcl_Obj *));
}
if (tailShift != 0 && tailSegmentLen != 0) {
+ /* T:listrep-1.7 */
ListSizeT tailStart = leadSegmentLen + numToDelete;
memmove(&listObjs[tailStart + tailShift],
&listObjs[tailStart],
@@ -2415,6 +2455,7 @@ Tcl_ListObjReplace(
}
if (numToInsert) {
/* Do NOT use ObjArrayCopy here since we have already incr'ed ref counts */
+ /* T:listrep-1.{1,3} */
memmove(&listObjs[leadSegmentLen + leadShift],
insertObjs,
numToInsert * sizeof(Tcl_Obj *));
@@ -2431,8 +2472,10 @@ Tcl_ListObjReplace(
} else {
/* Need a new span record */
if (listRep.storePtr->firstUsed == 0) {
+ /* T:listrep-1.7 */
listRep.spanPtr = NULL;
} else {
+ /* T:listrep-1.{1,3,6} */
listRep.spanPtr = ListSpanNew(listRep.storePtr->firstUsed,
listRep.storePtr->numUsed);
}