Skip to content

Commit df467e6

Browse files
committed
Change a few methods so they do what their name suggests (fix for #2765)
1 parent bed1cff commit df467e6

File tree

1 file changed

+77
-39
lines changed

1 file changed

+77
-39
lines changed

Source/SPDataStorage.m

+77-39
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
@interface SPDataStorage (Private_API)
3838

3939
- (void) _checkNewRow:(NSMutableArray *)aRow;
40+
- (void) _addRowUnsafeUnchecked:(NSMutableArray *)aRow;
4041

4142
@end
4243

@@ -107,6 +108,7 @@ - (void) setDataStorage:(SPMySQLStreamingResultStore *)newDataStorage updatingEx
107108

108109
/**
109110
* Return a mutable array containing the data for a specified row.
111+
* The returned array will be a shallow copy of the internal row object.
110112
*/
111113
- (NSMutableArray *) rowContentsAtIndex:(NSUInteger)anIndex
112114
{
@@ -117,12 +119,12 @@ - (NSMutableArray *) rowContentsAtIndex:(NSUInteger)anIndex
117119
NSMutableArray *editedRow = SPDataStorageGetEditedRow(editedRows, anIndex);
118120

119121
if (editedRow != NULL) {
120-
return editedRow;
122+
return [NSMutableArray arrayWithArray:editedRow]; //make a copy to not give away control of our internal state
121123
}
122124
}
123125

124126
// Otherwise, prepare to return the underlying storage row
125-
NSMutableArray *dataArray = SPMySQLResultStoreGetRow(dataStorage, anIndex);
127+
NSMutableArray *dataArray = SPMySQLResultStoreGetRow(dataStorage, anIndex); //returned array is already a copy
126128

127129
// Modify unloaded cells as appropriate
128130
for (NSUInteger i = 0; i < numberOfColumns; i++) {
@@ -252,11 +254,14 @@ - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state object
252254
// If an edited row exists for the supplied index, use that; otherwise use the underlying
253255
// storage row
254256
if (state->state < editedRowCount) {
255-
targetRow = SPDataStorageGetEditedRow(editedRows, state->state);
257+
NSMutableArray *internalRow = SPDataStorageGetEditedRow(editedRows, state->state);
258+
if(internalRow != NULL) {
259+
targetRow = [NSMutableArray arrayWithArray:internalRow]; //make a copy to not give away control of our internal state
260+
}
256261
}
257262

258263
if (targetRow == nil) {
259-
targetRow = SPMySQLResultStoreGetRow(dataStorage, state->state);
264+
targetRow = SPMySQLResultStoreGetRow(dataStorage, state->state); //returned array is already a copy
260265

261266
// Modify unloaded cells as appropriate
262267
for (NSUInteger i = 0; i < numberOfColumns; i++) {
@@ -287,16 +292,18 @@ - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state object
287292
*/
288293
- (void) addRowWithContents:(NSMutableArray *)aRow
289294
{
290-
@synchronized(self) {
291-
// Verify the row is of the correct length
292-
[self _checkNewRow:aRow];
293-
294-
// Add the new row to the editable store
295-
[editedRows addPointer:aRow];
296-
editedRowCount++;
297-
298-
// Update the underlying store as well to keep counts correct
299-
[dataStorage addDummyRow];
295+
// we can't just store the passed in array as that would give an outsider too much control of our internal state
296+
// (e.g. they could change the bounds after adding it, defeating the check below), so let's make a shallow copy.
297+
NSMutableArray *newArray = [[NSMutableArray alloc] initWithArray:aRow];
298+
@try {
299+
@synchronized(self) {
300+
// Verify the row is of the correct length
301+
[self _checkNewRow:newArray];
302+
[self _addRowUnsafeUnchecked:newArray];
303+
}
304+
}
305+
@finally {
306+
[newArray release];
300307
}
301308
}
302309

@@ -307,39 +314,58 @@ - (void) addRowWithContents:(NSMutableArray *)aRow
307314
*/
308315
- (void) insertRowContents:(NSMutableArray *)aRow atIndex:(NSUInteger)anIndex
309316
{
310-
@synchronized(self) {
311-
unsigned long long numberOfRows = SPMySQLResultStoreGetRowCount(dataStorage);
312-
313-
// Verify the row is of the correct length
314-
[self _checkNewRow:aRow];
315-
316-
// Throw an exception if the index is out of bounds
317-
if (anIndex > numberOfRows) {
318-
[NSException raise:NSRangeException format:@"Requested storage index (%llu) beyond bounds (%llu)", (unsigned long long)anIndex, numberOfRows];
319-
}
320-
321-
// If "inserting" at the end of the array just add a row
322-
if (anIndex == numberOfRows) {
323-
return [self addRowWithContents:aRow];
317+
// we can't just store the passed in array as that would give an outsider too much control of our internal state
318+
// (e.g. they could change the bounds after adding it, defeating the check below), so let's make a shallow copy.
319+
NSMutableArray *newArray = [[NSMutableArray alloc] initWithArray:aRow];
320+
@try {
321+
@synchronized(self) {
322+
unsigned long long numberOfRows = SPMySQLResultStoreGetRowCount(dataStorage);
323+
324+
// Verify the row is of the correct length
325+
[self _checkNewRow:newArray];
326+
327+
// Throw an exception if the index is out of bounds
328+
if (anIndex > numberOfRows) {
329+
[NSException raise:NSRangeException format:@"Requested storage index (%llu) beyond bounds (%llu)", (unsigned long long)anIndex, numberOfRows];
330+
}
331+
332+
// If "inserting" at the end of the array just add a row
333+
if (anIndex == numberOfRows) {
334+
[self _addRowUnsafeUnchecked:newArray];
335+
return;
336+
}
337+
338+
// Add the new row to the editable store
339+
[editedRows insertPointer:newArray atIndex:anIndex];
340+
editedRowCount++;
341+
342+
// Update the underlying store to keep counts and indices correct
343+
[dataStorage insertDummyRowAtIndex:anIndex];
324344
}
325-
326-
// Add the new row to the editable store
327-
[editedRows insertPointer:aRow atIndex:anIndex];
328-
editedRowCount++;
329-
330-
// Update the underlying store to keep counts and indices correct
331-
[dataStorage insertDummyRowAtIndex:anIndex];
345+
}
346+
@finally {
347+
[newArray release];
332348
}
333349
}
334350

335351
/**
336352
* Replace a row with contents of the supplied NSArray.
353+
*
354+
* Note that the supplied objects within the array are retained as a reference rather than copied.
337355
*/
338356
- (void) replaceRowAtIndex:(NSUInteger)anIndex withRowContents:(NSMutableArray *)aRow
339357
{
340-
@synchronized(self) {
341-
[self _checkNewRow:aRow];
342-
[editedRows replacePointerAtIndex:anIndex withPointer:aRow];
358+
// we can't just store the passed in array as that would give an outsider too much control of our internal state
359+
// (e.g. they could change the bounds after adding it, defeating the check below), so let's make a shallow copy.
360+
NSMutableArray *newArray = [[NSMutableArray alloc] initWithArray:aRow];
361+
@try {
362+
@synchronized(self) {
363+
[self _checkNewRow:newArray];
364+
[editedRows replacePointerAtIndex:anIndex withPointer:newArray];
365+
}
366+
}
367+
@finally {
368+
[newArray release];
343369
}
344370
}
345371

@@ -357,7 +383,7 @@ - (void) replaceObjectInRow:(NSUInteger)rowIndex column:(NSUInteger)columnIndex
357383

358384
// Make sure that the row in question is editable
359385
if (editableRow == nil) {
360-
editableRow = [self rowContentsAtIndex:rowIndex];
386+
editableRow = [self rowContentsAtIndex:rowIndex]; //already returns a copy, so we don't have to go via -replaceRowAtIndex:withRowContents:
361387
[editedRows replacePointerAtIndex:rowIndex withPointer:editableRow];
362388
}
363389
}
@@ -536,4 +562,16 @@ - (void) _checkNewRow:(NSMutableArray *)aRow
536562
}
537563
}
538564

565+
// DO NOT CALL THIS METHOD UNLESS YOU CURRENTLY HAVE A LOCK ON SELF!!!
566+
// DO NOT CALL THIS METHOD UNLESS YOU HAVE CALLED _checkNewRow: FIRST!
567+
- (void)_addRowUnsafeUnchecked:(NSMutableArray *)aRow
568+
{
569+
// Add the new row to the editable store
570+
[editedRows addPointer:aRow];
571+
editedRowCount++;
572+
573+
// Update the underlying store as well to keep counts correct
574+
[dataStorage addDummyRow];
575+
}
576+
539577
@end

0 commit comments

Comments
 (0)