37
37
@interface SPDataStorage (Private_API)
38
38
39
39
- (void ) _checkNewRow : (NSMutableArray *)aRow ;
40
+ - (void ) _addRowUnsafeUnchecked : (NSMutableArray *)aRow ;
40
41
41
42
@end
42
43
@@ -107,6 +108,7 @@ - (void) setDataStorage:(SPMySQLStreamingResultStore *)newDataStorage updatingEx
107
108
108
109
/* *
109
110
* 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.
110
112
*/
111
113
- (NSMutableArray *) rowContentsAtIndex : (NSUInteger )anIndex
112
114
{
@@ -117,12 +119,12 @@ - (NSMutableArray *) rowContentsAtIndex:(NSUInteger)anIndex
117
119
NSMutableArray *editedRow = SPDataStorageGetEditedRow (editedRows, anIndex);
118
120
119
121
if (editedRow != NULL ) {
120
- return editedRow;
122
+ return [ NSMutableArray arrayWithArray: editedRow]; // make a copy to not give away control of our internal state
121
123
}
122
124
}
123
125
124
126
// 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
126
128
127
129
// Modify unloaded cells as appropriate
128
130
for (NSUInteger i = 0 ; i < numberOfColumns; i++) {
@@ -252,11 +254,14 @@ - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state object
252
254
// If an edited row exists for the supplied index, use that; otherwise use the underlying
253
255
// storage row
254
256
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
+ }
256
261
}
257
262
258
263
if (targetRow == nil ) {
259
- targetRow = SPMySQLResultStoreGetRow (dataStorage, state->state );
264
+ targetRow = SPMySQLResultStoreGetRow (dataStorage, state->state ); // returned array is already a copy
260
265
261
266
// Modify unloaded cells as appropriate
262
267
for (NSUInteger i = 0 ; i < numberOfColumns; i++) {
@@ -287,16 +292,18 @@ - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state object
287
292
*/
288
293
- (void ) addRowWithContents : (NSMutableArray *)aRow
289
294
{
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 ];
300
307
}
301
308
}
302
309
@@ -307,39 +314,58 @@ - (void) addRowWithContents:(NSMutableArray *)aRow
307
314
*/
308
315
- (void ) insertRowContents : (NSMutableArray *)aRow atIndex : (NSUInteger )anIndex
309
316
{
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];
324
344
}
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 ];
332
348
}
333
349
}
334
350
335
351
/* *
336
352
* 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.
337
355
*/
338
356
- (void ) replaceRowAtIndex : (NSUInteger )anIndex withRowContents : (NSMutableArray *)aRow
339
357
{
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 ];
343
369
}
344
370
}
345
371
@@ -357,7 +383,7 @@ - (void) replaceObjectInRow:(NSUInteger)rowIndex column:(NSUInteger)columnIndex
357
383
358
384
// Make sure that the row in question is editable
359
385
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:
361
387
[editedRows replacePointerAtIndex: rowIndex withPointer: editableRow];
362
388
}
363
389
}
@@ -536,4 +562,16 @@ - (void) _checkNewRow:(NSMutableArray *)aRow
536
562
}
537
563
}
538
564
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
+
539
577
@end
0 commit comments