Adium

Changeset 23524

Show
Ignore:
Timestamp:
05/20/2008 07:55:56 PM (7 months ago)
Author:
evands
Message:
  • Avoid the possibility of modifying the set of list object observers being enumerated by copying/autoreleasing it before enumeration.
  • Keep track of observers removed while observers are being notified, and avoid messaging them subsequently. Fixes #9425, where I wrote:

The problem occurs when a list object observer is unregistered and deallocates in response to the status update being posted. In this specific case, AIContactMenu is a list object observer, responds to an update by informing its delegate, which happens to be an AIAccountSelectorView. AIAccountSelectorView destroys and then recreates its AIAccountMenu... and then the enumeration of the observers continues, eventually trying to message the old AIAccountMenu, which has now been deallocated. *boom*.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/Source/AdiumContactPropertiesObserverManager.h

    r23220 r23524  
    1212@interface AdiumContactPropertiesObserverManager : AIObject { 
    1313        //Status and Attribute updates 
    14     NSMutableSet                        *contactObservers;       
     14    NSMutableSet                        *contactObservers; 
     15        NSMutableSet                    *removedContactObservers; 
    1516    NSTimer                                     *delayedUpdateTimer; 
    1617    int                                         delayedStatusChanges; 
     
    2021 
    2122        BOOL                                    updatesAreDelayed; 
     23         
     24        BOOL                                    informingObservers; 
    2225        /* Only the contact controller can speak to us directly, and it's allowed to access these ivars */ 
    2326@public 
  • trunk/Source/AdiumContactPropertiesObserverManager.m

    r23227 r23524  
    249249#endif 
    250250    [contactObservers removeObject:[NSValue valueWithNonretainedObject:inObserver]]; 
     251         
     252        /* If we're in the middle of informing observers, we need to note this now-removed observer 
     253         * so that we don't attempt to message it during this iteration. 
     254         */ 
     255        if (informingObservers) { 
     256                if (!removedContactObservers) removedContactObservers = [[NSMutableSet alloc] init]; 
     257                [removedContactObservers addObject:[NSValue valueWithNonretainedObject:inObserver]]; 
     258        } 
    251259} 
    252260 
     
    323331        NSValue                 *observerValue; 
    324332         
     333        informingObservers = YES; 
     334 
    325335        //Let our observers know 
    326         enumerator = [contactObservers objectEnumerator]; 
     336        enumerator = [[[contactObservers copy] autorelease] objectEnumerator]; 
    327337        while ((observerValue = [enumerator nextObject])) { 
    328                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
    329338                id <AIListObjectObserver>       observer; 
    330339                NSSet                                           *newKeys; 
     340                 
     341                /* Skip any observer which has been removed while we were iterating over observers, 
     342                 * as we don't retain observers and therefore risk messaging a released object. 
     343                 */ 
     344                if (removedContactObservers && [removedContactObservers containsObject:observerValue]) 
     345                        continue; 
    331346                 
    332347                observer = [observerValue nonretainedObjectValue]; 
     
    341356                        [attrChange unionSet:newKeys]; 
    342357                } 
    343                 [pool release]; 
    344358        } 
    345359        //Send out the notification for other observers 
     
    349363                                                                                                                                                                                                 forKey:@"Keys"] : nil)]; 
    350364         
     365        informingObservers = NO; 
     366 
     367        //If we removed any observers while informing them, we don't need that information any more 
     368        if (removedContactObservers) { 
     369                [removedContactObservers release]; removedContactObservers = nil; 
     370        } 
     371 
    351372        return [attrChange autorelease]; 
    352373} 
     
    355376- (void)_updateAllAttributesOfObject:(AIListObject *)inObject 
    356377{ 
    357         NSEnumerator    *enumerator = [contactObservers objectEnumerator]; 
     378        NSEnumerator    *enumerator = [[[contactObservers copy] autorelease] objectEnumerator]; 
    358379        NSValue                 *observerValue; 
    359          
    360         while ((observerValue = [enumerator nextObject])) {              
     380 
     381        informingObservers = YES; 
     382 
     383        while ((observerValue = [enumerator nextObject])) { 
     384                /* Skip any observer which has been removed while we were iterating over observers, 
     385                 * as we don't retain observers and therefore risk messaging a released object. 
     386                 */ 
     387                if (removedContactObservers && [removedContactObservers containsObject:observerValue]) 
     388                        continue; 
     389 
    361390                id <AIListObjectObserver> observer = [observerValue nonretainedObjectValue]; 
    362391                 
    363392                [observer updateListObject:inObject keys:nil silent:YES]; 
    364393        } 
     394         
     395        informingObservers = NO; 
    365396} 
    366397