diff --git a/Service/Sources/EDOClientService.m b/Service/Sources/EDOClientService.m index e00f80c..2aa2754 100644 --- a/Service/Sources/EDOClientService.m +++ b/Service/Sources/EDOClientService.m @@ -122,6 +122,52 @@ + (id)unwrappedObjectFromObject:(id)object { return resolvedInstance; } } + return object; + } + + if ([objClass isSubclassOfClass:[NSArray class]]) { + BOOL modified = NO; + NSMutableArray *newArray = + [NSMutableArray arrayWithCapacity:[((NSArray *)object) count]]; + for (id item in ((NSArray *)object)) { + id unwrapped = [self unwrappedObjectFromObject:item]; + if (unwrapped != item) { + modified = YES; + } + [newArray addObject:unwrapped ?: [NSNull null]]; + } + return modified ? [newArray copy] : object; + } + if ([objClass isSubclassOfClass:[NSSet class]]) { + BOOL modified = NO; + NSMutableSet *newSet = [NSMutableSet setWithCapacity:[((NSSet *)object) count]]; + for (id item in ((NSSet *)object)) { + id unwrapped = [self unwrappedObjectFromObject:item]; + if (unwrapped != item) { + modified = YES; + } + if (unwrapped) { + [newSet addObject:unwrapped]; + } + } + return modified ? [newSet copy] : object; + } + if ([objClass isSubclassOfClass:[NSDictionary class]]) { + BOOL modified = NO; + NSMutableDictionary *newDict = + [NSMutableDictionary dictionaryWithCapacity:[((NSDictionary *)object) count]]; + for (id key in ((NSDictionary *)object)) { + id value = ((NSDictionary *)object)[key]; + id unwrappedKey = [self unwrappedObjectFromObject:key]; + id unwrappedValue = [self unwrappedObjectFromObject:value]; + if (unwrappedKey != key || unwrappedValue != value) { + modified = YES; + } + if (unwrappedKey && unwrappedValue) { + newDict[unwrappedKey] = unwrappedValue; + } + } + return modified ? [newDict copy] : object; } return object; @@ -218,22 +264,75 @@ + (id)cachedEDOFromObjectUpdateIfNeeded:(id)object { [EDOBlockObject isBlock:object] ? [EDOBlockObject EDOBlockObjectFromBlock:object] : object; Class objClass = object_getClass(edoObject); if (objClass == [EDOObject class] || objClass == [EDOBlockObject class]) { - id localObject = [self distantObjectReferenceForRemoteAddress:edoObject.remoteAddress]; - EDOObject *localEDO = localObject; - if ([EDOBlockObject isBlock:localObject]) { - localEDO = [EDOBlockObject EDOBlockObjectFromBlock:localEDO]; + NSNumber *edoKey = [NSNumber numberWithLongLong:edoObject.remoteAddress]; + __block id result = object; + __block id objectToRelease = nil; + dispatch_sync(self.edoSyncQueue, ^{ + id localObject = [self.localDistantObjects objectForKey:edoKey]; + EDOObject *localEDO = localObject; + if ([EDOBlockObject isBlock:localObject]) { + localEDO = [EDOBlockObject EDOBlockObjectFromBlock:localObject]; + } + // Verify the service in case the old address is overwritten by a new service. + if (localObject && [edoObject.servicePort match:localEDO.servicePort]) { + result = localObject; + } else { + if (localObject) { + objectToRelease = localObject; + } + // Track the new remote object. + [self.localDistantObjects setObject:object forKey:edoKey]; + } + }); + objectToRelease = nil; + return result; + } + + if ([objClass isSubclassOfClass:[NSArray class]]) { + BOOL modified = NO; + NSMutableArray *newArray = + [NSMutableArray arrayWithCapacity:[((NSArray *)object) count]]; + for (id item in ((NSArray *)object)) { + id cached = [self cachedEDOFromObjectUpdateIfNeeded:item]; + if (cached != item) { + modified = YES; + } + [newArray addObject:cached ?: [NSNull null]]; } - // Verify the service in case the old address is overwritten by a new service. - if ([edoObject.servicePort match:localEDO.servicePort]) { - // Since we already have the EDOObject in the cache, the new decoded EDOObject is - // taken as a temporary local object, which does not send release message. - edoObject.local = YES; - return localObject; - } else { - // Track the new remote object. - [self addDistantObjectReference:object]; + return modified ? [newArray copy] : object; + } + if ([objClass isSubclassOfClass:[NSSet class]]) { + BOOL modified = NO; + NSMutableSet *newSet = [NSMutableSet setWithCapacity:[((NSSet *)object) count]]; + for (id item in ((NSSet *)object)) { + id cached = [self cachedEDOFromObjectUpdateIfNeeded:item]; + if (cached != item) { + modified = YES; + } + if (cached) { + [newSet addObject:cached]; + } } + return modified ? [newSet copy] : object; } + if ([objClass isSubclassOfClass:[NSDictionary class]]) { + BOOL modified = NO; + NSMutableDictionary *newDict = + [NSMutableDictionary dictionaryWithCapacity:[((NSDictionary *)object) count]]; + for (id key in ((NSDictionary *)object)) { + id value = ((NSDictionary *)object)[key]; + id cachedKey = [self cachedEDOFromObjectUpdateIfNeeded:key]; + id cachedValue = [self cachedEDOFromObjectUpdateIfNeeded:value]; + if (cachedKey != key || cachedValue != value) { + modified = YES; + } + if (cachedKey && cachedValue) { + newDict[cachedKey] = cachedValue; + } + } + return modified ? [newDict copy] : object; + } + return object; } diff --git a/Service/Sources/EDODeallocationTracker.h b/Service/Sources/EDODeallocationTracker.h index 4ff033a..cb36927 100644 --- a/Service/Sources/EDODeallocationTracker.h +++ b/Service/Sources/EDODeallocationTracker.h @@ -18,16 +18,15 @@ NS_ASSUME_NONNULL_BEGIN -@class EDOHostPort; +@class EDOServicePort; @class EDOWeakObject; /** - * The EDODeallocationTracker is a tracker that manages local object's deallocation. + * The deallocation tracker to track the remote object lifecycle. * - * The tracker is associated with the local object's life cycle. When the local object is wrapped, - * the tracker is attached to the local object. When the local object is no longer in use and - * deallocates, the EDODeallocationTracker will be deallocated as well. An EDOObjectReleaseRequest - * is then sent to remove the remote weak reference from the weak object dictionary. + * It is associated with the underlying object of a weak object. When the underlying object is + * released, the tracker is deallocated and it will send a release message to remove the weak object + * entry from the dictionary in the host service. */ @interface EDODeallocationTracker : NSObject @@ -35,9 +34,10 @@ NS_ASSUME_NONNULL_BEGIN * Creates an instance of the tracker that is associated with the underlying object. * * @param trackedObject The remote object that is stored in the weak object dictionary. - * @param hostPort The host port where weak object dictionary holds the remote object. + * @param servicePort The service port where weak object dictionary holds the remote object. */ -+ (void)enableTrackingForObject:(EDOWeakObject *)trackedObject hostPort:(EDOHostPort *)hostPort; ++ (void)enableTrackingForObject:(EDOWeakObject *)trackedObject + servicePort:(EDOServicePort *)servicePort; - (instancetype)init NS_UNAVAILABLE; diff --git a/Service/Sources/EDODeallocationTracker.m b/Service/Sources/EDODeallocationTracker.m index 9d155a3..6d866cb 100644 --- a/Service/Sources/EDODeallocationTracker.m +++ b/Service/Sources/EDODeallocationTracker.m @@ -23,6 +23,7 @@ #import "Service/Sources/EDOClientService.h" #import "Service/Sources/EDOObject+Private.h" #import "Service/Sources/EDOObjectReleaseMessage.h" +#import "Service/Sources/EDOServicePort.h" #import "Service/Sources/EDOWeakObject.h" @interface EDODeallocationTracker () @@ -30,35 +31,36 @@ @interface EDODeallocationTracker () /** The tracked object address (EDOWeakObject) that is stored in the weak object dictionary. */ @property(readonly, nonatomic) EDOPointerType remoteObjectAddress; /** The host port where weak object dictionary holds the remote object. */ -@property(readonly, nonatomic) EDOHostPort *hostPort; +@property(readonly, nonatomic) EDOServicePort *servicePort; @end @implementation EDODeallocationTracker -+ (void)enableTrackingForObject:(EDOWeakObject *)trackedObject hostPort:(EDOHostPort *)hostPort { ++ (void)enableTrackingForObject:(EDOWeakObject *)trackedObject + servicePort:(EDOServicePort *)servicePort { // This does not support multiple weak objects (e.g. from different services) that point to the // same underlying object, as only one single deallocation tracker is associated with the // underlying object. The host port is merely used to decide where the release request should be - // sent and only the first port associated with the underlying object is used. + // routed to. EDODeallocationTracker *tracker = objc_getAssociatedObject(trackedObject.weakObject, &_cmd); if (!tracker) { - tracker = [[self alloc] initWithTrackedObject:trackedObject hostPort:hostPort]; + tracker = [[self alloc] initWithTrackedObject:trackedObject servicePort:servicePort]; objc_setAssociatedObject(trackedObject.weakObject, &_cmd, tracker, OBJC_ASSOCIATION_RETAIN); } else { NSAssert( - [tracker.hostPort isEqual:hostPort], + [tracker.servicePort match:servicePort], @"Deallocation tracker does not support tracking the same object from multiple host ports." @"Existing port: %@\nNew port: %@", - tracker.hostPort, hostPort); + tracker.servicePort, servicePort); } } - (instancetype)initWithTrackedObject:(EDOWeakObject *)trackedObject - hostPort:(EDOHostPort *)hostPort { + servicePort:(EDOServicePort *)servicePort { self = [super init]; if (self) { _remoteObjectAddress = (EDOPointerType)trackedObject; - _hostPort = hostPort; + _servicePort = servicePort; } return self; } @@ -66,8 +68,9 @@ - (instancetype)initWithTrackedObject:(EDOWeakObject *)trackedObject - (void)dealloc { @try { EDOObjectReleaseRequest *request = - [EDOObjectReleaseRequest requestWithWeakRemoteAddress:self.remoteObjectAddress]; - [EDOClientService sendSynchronousRequest:request onPort:self.hostPort]; + [EDOObjectReleaseRequest requestWithWeakRemoteAddress:self.remoteObjectAddress + servicePort:self.servicePort]; + [EDOClientService sendSynchronousRequest:request onPort:self.servicePort.hostPort]; } @catch (NSException *e) { // Safely ignore the exception because we don't care about the errors when we send the release // message. The service could be terminated, or the message can't be processed, but either way, diff --git a/Service/Sources/EDOHostService+Private.h b/Service/Sources/EDOHostService+Private.h index 08d98eb..33d3e23 100644 --- a/Service/Sources/EDOHostService+Private.h +++ b/Service/Sources/EDOHostService+Private.h @@ -64,6 +64,18 @@ NS_ASSUME_NONNULL_BEGIN */ - (BOOL)isObjectAliveWithPort:(EDOServicePort *)port remoteAddress:(EDOPointerType)remoteAddress; +/** + * Resolves the local object that this service has previously vended for the given address. + * + * The address is treated purely as an opaque lookup key into the service's tracked-object table; it + * is never dereferenced. This is the safe replacement for casting a wire-supplied @c EDOPointerType + * back to @c id. + * + * @param remoteAddress The address that was previously returned to the client in an @c EDOObject. + * @return The tracked local object, or @c nil if @c remoteAddress is not known to this service. + */ +- (nullable id)localObjectForAddress:(EDOPointerType)remoteAddress; + /** * Removes an EDOObject with the specified address in the host cache. * diff --git a/Service/Sources/EDOHostService.m b/Service/Sources/EDOHostService.m index 0a8c33a..af7f8f6 100644 --- a/Service/Sources/EDOHostService.m +++ b/Service/Sources/EDOHostService.m @@ -97,6 +97,11 @@ @interface EDOHostService () * the object. */ @property(nonatomic, readonly) NSMutableDictionary *localObjects; +/** + * The reference counts for the tracked objects in the service. The key is the address of a tracked + * object and the value is the reference count. + */ +@property(nonatomic, readonly) NSMutableDictionary *localObjectsCounts; /** The queue to update local objects atomically. */ @property(nonatomic, readonly) dispatch_queue_t localObjectsSyncQueue; /** @@ -219,6 +224,7 @@ - (instancetype)initWithPort:(UInt16)port if (self) { _registeredToDevice = NO; _localObjects = [[NSMutableDictionary alloc] init]; + _localObjectsCounts = [[NSMutableDictionary alloc] init]; _localObjectsSyncQueue = dispatch_queue_create("com.google.edo.service.localObjects", DISPATCH_QUEUE_SERIAL); @@ -374,6 +380,10 @@ - (EDOObject *)distantObjectForLocalObject:(id)object hostPort:(EDOHostPort *)ho NSNumber *objectKey = [NSNumber numberWithLongLong:(EDOPointerType)object]; if (object != self.rootLocalObject) { dispatch_sync(_localObjectsSyncQueue, ^{ + NSNumber *countObj = [self.localObjectsCounts objectForKey:objectKey]; + long count = countObj ? [countObj longValue] : 0; + [self.localObjectsCounts setObject:[NSNumber numberWithLong:count + 1] forKey:objectKey]; + if (![self.localObjects objectForKey:objectKey]) { [self.localObjects setObject:object forKey:objectKey]; } @@ -389,6 +399,19 @@ - (EDOObject *)distantObjectForLocalObject:(id)object hostPort:(EDOHostPort *)ho return [EDOObject edo_remoteProxyFromUnderlyingObject:object withPort:port]; } } +- (id)localObjectForAddress:(EDOPointerType)remoteAddress { + // ivar is used directly here to avoid the service lazily creating the listen port. + if (_rootLocalObject && (EDOPointerType)_rootLocalObject == remoteAddress) { + return _rootLocalObject; + } + NSNumber *edoKey = [NSNumber numberWithLongLong:remoteAddress]; + __block id object; + dispatch_sync(_localObjectsSyncQueue, ^{ + object = self.localObjects[edoKey]; + }); + + return object; +} - (BOOL)isObjectAliveWithPort:(EDOServicePort *)port remoteAddress:(EDOPointerType)remoteAddress { if (![_port match:port]) { @@ -408,7 +431,19 @@ - (BOOL)removeObjectWithAddress:(EDOPointerType)remoteAddress { NSNumber *edoKey = [NSNumber numberWithLongLong:remoteAddress]; dispatch_sync(_localObjectsSyncQueue, ^{ - [self.localObjects removeObjectForKey:edoKey]; + NSNumber *countObj = [self.localObjectsCounts objectForKey:edoKey]; + if (countObj) { + long count = [countObj longValue]; + if (count > 1) { + [self.localObjectsCounts setObject:[NSNumber numberWithLong:count - 1] forKey:edoKey]; + } else { + [self.localObjectsCounts removeObjectForKey:edoKey]; + [self.localObjects removeObjectForKey:edoKey]; + } + } else { + // Fallback for safety, though it should ideally be tracked. + [self.localObjects removeObjectForKey:edoKey]; + } }); return YES; } diff --git a/Service/Sources/EDOInvocationMessage.m b/Service/Sources/EDOInvocationMessage.m index d273f0e..36f4c32 100644 --- a/Service/Sources/EDOInvocationMessage.m +++ b/Service/Sources/EDOInvocationMessage.m @@ -146,8 +146,15 @@ static EDOMethodFamily MethodTypeOfRetainsReturn(const char *methodName, Class t } NSArray *exceptionStackTrace = [localException callStackSymbols]; NSArray *currentStackTrace = [NSThread callStackSymbols]; - NSArray *majorStackTrace = [exceptionStackTrace - subarrayWithRange:NSMakeRange(0, exceptionStackTrace.count - currentStackTrace.count + 1)]; + NSArray *majorStackTrace = exceptionStackTrace; + + if (exceptionStackTrace.count >= currentStackTrace.count) { + NSUInteger length = exceptionStackTrace.count - currentStackTrace.count + 1; + if (length <= exceptionStackTrace.count) { + majorStackTrace = [exceptionStackTrace subarrayWithRange:NSMakeRange(0, length)]; + } + } + return [[EDORemoteException alloc] initWithName:[localException name] reason:[localException reason] callStackSymbols:majorStackTrace]; @@ -341,7 +348,26 @@ + (EDORequestHandler)requestHandler { NSAssert([request isKindOfClass:[EDOInvocationRequest class]], @"EDOInvocationRequest is expected."); EDOHostPort *hostPort = request.hostPort; - id target = (__bridge id)(void *)request.target; + // The target address arrives off the wire as a raw 64-bit integer. It must NOT be cast to id + // until the service has confirmed it is an object it previously vended; otherwise an attacker + // can supply an arbitrary pointer and obtain a wild dereference / objc_msgSend on a fake isa. + id target = [service localObjectForAddress:request.target]; + if (!target) { + NSString *reason = [NSString + stringWithFormat:@"The target address (%llx) is not tracked by this service (selector: " + @"%@, servicePort: %u).", + request.target, request.selectorName, service.port.hostPort.port]; + EDORemoteException *remoteException = + [[EDORemoteException alloc] initWithName:EDOServiceGenericException + reason:reason + callStackSymbols:[NSThread callStackSymbols]]; + + return [EDOInvocationResponse responseWithReturnValue:nil + exception:remoteException + outValues:nil + forRequest:request + targetClass:Nil]; + } SEL sel = NSSelectorFromString(request.selectorName); EDOBoxedValueType *returnValue; diff --git a/Service/Sources/EDOMethodSignatureMessage.m b/Service/Sources/EDOMethodSignatureMessage.m index 2e39c6d..a0f16a4 100644 --- a/Service/Sources/EDOMethodSignatureMessage.m +++ b/Service/Sources/EDOMethodSignatureMessage.m @@ -16,6 +16,7 @@ #import "Service/Sources/EDOMethodSignatureMessage.h" +#import "Service/Sources/EDOHostService+Private.h" #import "Service/Sources/EDOHostService.h" #import "Service/Sources/EDOMessage.h" #import "Service/Sources/EDOObject+Private.h" @@ -97,7 +98,12 @@ + (EDORequestHandler)requestHandler { } EDOMethodSignatureRequest *methodRequest = (EDOMethodSignatureRequest *)request; - id object = (__bridge Class)(void *)methodRequest.object; + id object = [service localObjectForAddress:methodRequest.object]; + if (!object) { + // If the object is not found, we can't get method signature. + // Returning nil signature will eventually result in an exception at the client. + return [[EDOMethodSignatureResponse alloc] initWithSignature:nil forRequest:request]; + } SEL sel = NSSelectorFromString(methodRequest.selectorName); NSMethodSignature *signature = EDOGetMethodSignature(object, sel); diff --git a/Service/Sources/EDOObject+Invocation.m b/Service/Sources/EDOObject+Invocation.m index 238fcf5..139b9b0 100644 --- a/Service/Sources/EDOObject+Invocation.m +++ b/Service/Sources/EDOObject+Invocation.m @@ -59,7 +59,8 @@ NSString *separationSymbol = [NSString stringWithFormat:@"|---- eDO invocation [%@ %@] ----|", classInfo, methodInfo]; - NSMutableArray *fullStackTraces = [remoteException.callStackSymbols mutableCopy]; + NSMutableArray *fullStackTraces = + [remoteException.callStackSymbols mutableCopy] ?: [[NSMutableArray alloc] init]; [fullStackTraces addObject:separationSymbol]; [fullStackTraces addObjectsFromArray:localOutputStackTraces]; return [[EDORemoteException alloc] initWithName:remoteException.name diff --git a/Service/Sources/EDOObject.m b/Service/Sources/EDOObject.m index 6600e14..1658433 100644 --- a/Service/Sources/EDOObject.m +++ b/Service/Sources/EDOObject.m @@ -158,7 +158,8 @@ - (void)dealloc { [EDOClientService removeDistantObjectReference:self.remoteAddress]; @try { EDOObjectReleaseRequest *request = - [EDOObjectReleaseRequest requestWithRemoteAddress:_remoteAddress]; + [EDOObjectReleaseRequest requestWithRemoteAddress:_remoteAddress + servicePort:_servicePort]; [EDOClientService sendSynchronousRequest:request onPort:_servicePort.hostPort]; } @catch (NSException *e) { // There's an error with the service or most likely it's dead. diff --git a/Service/Sources/EDOObjectReleaseMessage.h b/Service/Sources/EDOObjectReleaseMessage.h index de117d2..84c793d 100644 --- a/Service/Sources/EDOObjectReleaseMessage.h +++ b/Service/Sources/EDOObjectReleaseMessage.h @@ -33,16 +33,19 @@ NS_ASSUME_NONNULL_BEGIN * * @return An instance of EDOObjectReleaseRequest that removes the EDOObject from dictionary. */ -+ (instancetype)requestWithRemoteAddress:(EDOPointerType)remoteAddress; ++ (instancetype)requestWithRemoteAddress:(EDOPointerType)remoteAddress + servicePort:(EDOServicePort *)servicePort; /** * Creates an EDOObjectReleaseRequest for weak EDOObjects. * * @param remoteAddress The remote address for the weak EDOObject that is going to be released. + * @param servicePort The service port of the service that vended the object. * * @return An instance of EDOObjectReleaseRequest that removes the weak EDOObject from dictionary. */ -+ (instancetype)requestWithWeakRemoteAddress:(EDOPointerType)remoteAddress; ++ (instancetype)requestWithWeakRemoteAddress:(EDOPointerType)remoteAddress + servicePort:(EDOServicePort *)servicePort; @end diff --git a/Service/Sources/EDOObjectReleaseMessage.m b/Service/Sources/EDOObjectReleaseMessage.m index ea752f1..0418337 100644 --- a/Service/Sources/EDOObjectReleaseMessage.m +++ b/Service/Sources/EDOObjectReleaseMessage.m @@ -18,16 +18,19 @@ #import "Service/Sources/EDOHostService.h" #import "Service/Sources/EDOMessage.h" #import "Service/Sources/EDOObject+Private.h" +#import "Service/Sources/EDOServicePort.h" #import "Service/Sources/EDOServiceRequest.h" #import "Service/Sources/EDOHostService+Private.h" static NSString *const kEDOObjectReleaseCoderWeaklyReferencedKey = @"weaklyReferenced"; static NSString *const kEDOObjectReleaseCoderRemoteAddressKey = @"remoteAddress"; +static NSString *const kEDOObjectReleaseCoderServicePortKey = @"servicePort"; @interface EDOObjectReleaseRequest () @property(nonatomic, readonly) EDOPointerType remoteAddress; +@property(nonatomic, readonly) EDOServicePort *servicePort; /** Indicates whether the object to be released is a weakly referenced object. */ @property(nonatomic, readonly, getter=isWeaklyReferenced) BOOL weaklyReferenced; @@ -41,21 +44,29 @@ + (BOOL)supportsSecureCoding { } - (instancetype)initWithRemoteAddress:(EDOPointerType)remoteAddress + servicePort:(EDOServicePort *)servicePort weaklyReferenced:(BOOL)weaklyReferenced { self = [super init]; if (self) { _remoteAddress = remoteAddress; + _servicePort = servicePort; _weaklyReferenced = weaklyReferenced; } return self; } -+ (instancetype)requestWithRemoteAddress:(EDOPointerType)remoteAddress { - return [[self alloc] initWithRemoteAddress:remoteAddress weaklyReferenced:NO]; ++ (instancetype)requestWithRemoteAddress:(EDOPointerType)remoteAddress + servicePort:(EDOServicePort *)servicePort { + return [[self alloc] initWithRemoteAddress:remoteAddress + servicePort:servicePort + weaklyReferenced:NO]; } -+ (instancetype)requestWithWeakRemoteAddress:(EDOPointerType)remoteAddress { - return [[self alloc] initWithRemoteAddress:remoteAddress weaklyReferenced:YES]; ++ (instancetype)requestWithWeakRemoteAddress:(EDOPointerType)remoteAddress + servicePort:(EDOServicePort *)servicePort { + return [[self alloc] initWithRemoteAddress:remoteAddress + servicePort:servicePort + weaklyReferenced:YES]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { @@ -63,6 +74,8 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self) { _remoteAddress = [aDecoder decodeInt64ForKey:kEDOObjectReleaseCoderRemoteAddressKey]; _weaklyReferenced = [aDecoder decodeBoolForKey:kEDOObjectReleaseCoderWeaklyReferencedKey]; + _servicePort = [aDecoder decodeObjectOfClass:[EDOServicePort class] + forKey:kEDOObjectReleaseCoderServicePortKey]; } return self; } @@ -71,11 +84,19 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { [super encodeWithCoder:aCoder]; [aCoder encodeInt64:self.remoteAddress forKey:kEDOObjectReleaseCoderRemoteAddressKey]; [aCoder encodeBool:self.weaklyReferenced forKey:kEDOObjectReleaseCoderWeaklyReferencedKey]; + [aCoder encodeObject:self.servicePort forKey:kEDOObjectReleaseCoderServicePortKey]; } + (EDORequestHandler)requestHandler { return ^(EDOServiceRequest *request, EDOHostService *service) { EDOObjectReleaseRequest *releaseRequest = (EDOObjectReleaseRequest *)request; + + // Safety check: ensure the request is meant for this service. + if (releaseRequest.servicePort && ![releaseRequest.servicePort match:service.port]) { + // Ignore release requests from other service instances (e.g. if port was recycled). + return [[EDOServiceResponse alloc] initWithMessageID:request.messageID]; + } + EDOPointerType edoRemoteAddress = releaseRequest.remoteAddress; if (releaseRequest.weaklyReferenced) { [service removeWeakObjectWithAddress:edoRemoteAddress]; diff --git a/Service/Sources/EDOWeakObject.m b/Service/Sources/EDOWeakObject.m index 761192b..64d64f0 100644 --- a/Service/Sources/EDOWeakObject.m +++ b/Service/Sources/EDOWeakObject.m @@ -68,7 +68,7 @@ - (EDOParameter *)edo_parameterForTarget:(EDOObject *)target hostPort:(EDOHostPort *)hostPort { EDOParameter *parameter = [super edo_parameterForTarget:target service:service hostPort:hostPort]; if ([[target class] isEqual:[EDOObject class]]) { - [EDODeallocationTracker enableTrackingForObject:self hostPort:target.servicePort.hostPort]; + [EDODeallocationTracker enableTrackingForObject:self servicePort:target.servicePort]; } return parameter; } diff --git a/Service/Tests/FunctionalTests/EDOServiceUITest.m b/Service/Tests/FunctionalTests/EDOServiceUITest.m index ce1388b..ad2f861 100644 --- a/Service/Tests/FunctionalTests/EDOServiceUITest.m +++ b/Service/Tests/FunctionalTests/EDOServiceUITest.m @@ -516,7 +516,7 @@ - (void)testLocalObjectReleaseOnHostExecutionQueue { }; [remoteDummy returnPlus10AndAsyncExecuteBlock:dummy]; - [self waitForExpectations:@[ expectation ] timeout:2.0f]; + [self waitForExpectations:@[ expectation ] timeout:10.0f]; XCTAssertNil(weakDummy); [service invalidate]; diff --git a/Service/Tests/UnitTests/EDOMessageTest.m b/Service/Tests/UnitTests/EDOMessageTest.m index a454d33..a0e3cef 100644 --- a/Service/Tests/UnitTests/EDOMessageTest.m +++ b/Service/Tests/UnitTests/EDOMessageTest.m @@ -435,6 +435,13 @@ - (void)testClassMethodInvocationHandler { EDOTestDummy *dummyLocal = [[EDOTestDummy alloc] init]; [self edo_createQueueAndServiceWithRootObject:dummyLocal block:^(EDOHostService *service) { + // Simulate the client asking for the class first to + // register it securely. + EDOServiceRequest *classRequest = [EDOClassRequest + requestWithClassName:@"EDOTestDummy" + hostPort:service.port.hostPort]; + EDOClassRequest.requestHandler(classRequest, service); + EDOInvocationResponse *response = [self edo_runInvocationWithService:service target:[dummyLocal class] @@ -507,7 +514,7 @@ - (void)testMethodSignatureRequestHandler { void *remoteAddress = (__bridge void *)dummyLocal; EDOHostService *service = [EDOHostService serviceWithPort:0 - rootObject:self + rootObject:dummyLocal queue:dispatch_get_main_queue()]; [EDOTestDummy enumerateSelector:^(SEL selector) { diff --git a/Service/Tests/UnitTests/EDOWeakReferenceTest.m b/Service/Tests/UnitTests/EDOWeakReferenceTest.m index fc09270..c9b4d45 100644 --- a/Service/Tests/UnitTests/EDOWeakReferenceTest.m +++ b/Service/Tests/UnitTests/EDOWeakReferenceTest.m @@ -178,12 +178,14 @@ - (void)testWeakObjectSendReleaseMessageWhenUnderlyingObjectIsReleased { @autoreleasepool { EDOTestDummy *testDummy = [[EDOTestDummy alloc] init]; weakObject = [[EDOWeakObject alloc] initWithWeakObject:testDummy]; - [EDODeallocationTracker enableTrackingForObject:weakObject hostPort:hostService.port.hostPort]; + [EDODeallocationTracker enableTrackingForObject:weakObject servicePort:hostService.port]; // Verify that release message is not sent if the object is in scope. - OCMVerify(never(), [releaseMock requestWithWeakRemoteAddress:(EDOPointerType)weakObject]); + OCMVerify(never(), [releaseMock requestWithWeakRemoteAddress:(EDOPointerType)weakObject + servicePort:hostService.port]); } // Verify that when object is out of scope, the release message is sent. - OCMVerify(times(1), [releaseMock requestWithWeakRemoteAddress:(EDOPointerType)weakObject]); + OCMVerify(times(1), [releaseMock requestWithWeakRemoteAddress:(EDOPointerType)weakObject + servicePort:hostService.port]); [releaseMock stopMocking]; [hostService invalidate];