{"action":"created","comment":{"url":"https://api.github.com/repos/Microsoft/WinObjC/pulls/comments/116568059","pull_request_review_id":38198684,"id":116568059,"diff_hunk":"@@ -516,169 +515,292 @@ - (void)removeObserver:(id)observer forKeyPath:(NSString*)keyPath {\n     [self removeObserver:observer forKeyPath:keyPath context:NULL];\n }\n \n-static id _valueForPendingChange(\n-    id notifyingObject, NSString* key, id rootObject, NSString* keypath, _NSKVOKeyObserver* keyObserver, NSDictionary* pendingChange) {\n-    id value = nil;\n-    NSIndexSet* indexes = [pendingChange objectForKey:NSKeyValueChangeIndexesKey];\n-    if (!indexes) {\n-        value = [rootObject valueForKeyPath:keypath];\n-    } else {\n-        NSArray* collection = [notifyingObject valueForKey:key];\n-        NSString* restOfKeypath = keyObserver.restOfKeypath;\n-        value = restOfKeypath.length > 0 ? [collection valueForKeyPath:restOfKeypath] : collection;\n-        if ([value respondsToSelector:@selector(objectsAtIndexes:)]) {\n-            value = [value objectsAtIndexes:indexes];\n-        }\n+// Reference platform does not provide the Set Mutation Kind in the changes dictionary, just shows which elements were\n+// inserted/removed/replaced\n+static inline NSKeyValueChange _changeFromSetMutationKind(NSKeyValueSetMutationKind kind) {\n+    switch (kind) {\n+        case NSKeyValueUnionSetMutation:\n+            return NSKeyValueChangeInsertion;\n+        case NSKeyValueMinusSetMutation:\n+        case NSKeyValueIntersectSetMutation:\n+            return NSKeyValueChangeRemoval;\n+        default:\n+            return NSKeyValueChangeReplacement;\n     }\n+}\n \n-    if (!value) {\n-        value = [NSNull null];\n+static inline id _valueForPendingChangeAtIndexes(\n+    id notifyingObject, NSString* key, id rootObject, NSString* keypath, _NSKVOKeyObserver* keyObserver, NSIndexSet* indexes) {\n+    id collection = [notifyingObject valueForKey:key];\n+    NSString* restOfKeypath = keyObserver.restOfKeypath;\n+    id value = restOfKeypath.length > 0 ? [collection valueForKeyPath:restOfKeypath] : collection;\n+    if ([value respondsToSelector:@selector(objectsAtIndexes:)]) {\n+        value = [value objectsAtIndexes:indexes];\n     }\n-    return value;\n+\n+    return value ?: [NSNull null];\n }\n \n-static void _dispatchWillChange(id notifyingObject, NSString* key, NSMutableDictionary* change) {\n+// void TFunc(_NSKVOKeyObserver* keybserver);\n+template <typename TFunc>\n+inline static void _dispatchWillChange(id notifyingObject, NSString* key, TFunc&& func) {\n     _NSKVOObservationInfo* observationInfo = (__bridge _NSKVOObservationInfo*)[notifyingObject observationInfo];\n-\n-    NSUInteger changeKind = [change[NSKeyValueChangeKindKey] unsignedIntegerValue];\n     for (_NSKVOKeyObserver* keyObserver in [observationInfo observersForKey:key]) {\n         _NSKVOKeypathObserver* keypathObserver = keyObserver.keypathObserver;\n \n-        if (![keypathObserver pushWillChange]) {\n-            // Skip any keypaths that are in the process of changing.\n-            continue;\n-        } else {\n-            NSKeyValueObservingOptions options = keypathObserver.options;\n-            id rootObject = keypathObserver.object;\n+        // Skip any keypaths that are in the process of changing.\n+        if ([keypathObserver pushWillChange]) {\n+            // Call into the lambda function, which will do the actual set-up and notifying if necessary\n+            func(keyObserver);\n+\n+            // This must happen regardless of whether we are currently notifying.\n+            _removeNestedObserversAndOptionallyDependents(keyObserver, false);\n+        }\n+    }\n+}\n+\n+// void TFunc(_NSKVOKeyObserver* keyObserver, NSMutableDictionary* keypathValueCache);\n+template <typename TFunc>\n+static void _dispatchDidChange(id notifyingObject, NSString* key, TFunc&& func) {\n+    _NSKVOObservationInfo* observationInfo = (__bridge _NSKVOObservationInfo*)[notifyingObject observationInfo];\n+    NSArray<_NSKVOKeyObserver*>* observers = [observationInfo observersForKey:key];\n+    NSMutableDictionary* keypathValueCache = [NSMutableDictionary dictionaryWithCapacity:[observers count]];\n+    for (_NSKVOKeyObserver* keyObserver in [observers reverseObjectEnumerator]) {\n+        // This must happen regardless of whether we are currently notifying.\n+        _addNestedObserversAndOptionallyDependents(keyObserver, false);\n+\n+        // Skip any keypaths that are in the process of changing.\n+        _NSKVOKeypathObserver* keypathObserver = keyObserver.keypathObserver;\n+        if ([keypathObserver popDidChange]) {\n+            func(keyObserver, keypathValueCache);\n+\n             id observer = keypathObserver.observer;\n             NSString* keypath = keypathObserver.keypath;\n+            id rootObject = keypathObserver.object;\n+            NSKeyValueObservingOptions options = keypathObserver.options;\n+            NSMutableDictionary* change = keypathObserver.pendingChange;\n             void* context = keypathObserver.context;\n+            [observer observeValueForKeyPath:keypath ofObject:rootObject change:change context:context];\n+            keypathObserver.pendingChange = nil;\n+        }\n+    }\n+}\n \n-            // The reference platform does not support to-many mutations on nested keypaths.\n-            // We have to treat them as to-one mutations to support aggregate functions.\n-            if (changeKind != NSKeyValueChangeSetting && keyObserver.restOfKeypathObserver) {\n-                // This only needs to be done in willChange because didChange derives from the existing changeset.\n-                change[NSKeyValueChangeKindKey] = @(changeKind = NSKeyValueChangeSetting);\n-                [change removeObjectForKey:NSKeyValueChangeIndexesKey];\n-            }\n+/**\n+@Status Interoperable\n+*/\n+- (void)willChangeValueForKey:(NSString*)key {\n+    if ([self observationInfo]) {\n+        NSMutableDictionary* change = [NSMutableDictionary dictionaryWithObject:@(NSKeyValueChangeSetting) forKey:NSKeyValueChangeKindKey];","path":"Frameworks/Foundation/NSKVOSupport.mm","position":130,"original_position":130,"commit_id":"222696766ec66a7ec4571e9b6cdf729790c3b821","original_commit_id":"222696766ec66a7ec4571e9b6cdf729790c3b821","user":{"login":"DHowett-MSFT","id":14316954,"avatar_url":"https://avatars1.githubusercontent.com/u/14316954?v=3","gravatar_id":"","url":"https://api.github.com/users/DHowett-MSFT","html_url":"https://github.com/DHowett-MSFT","followers_url":"https://api.github.com/users/DHowett-MSFT/followers","following_url":"https://api.github.com/users/DHowett-MSFT/following{/other_user}","gists_url":"https://api.github.com/users/DHowett-MSFT/gists{/gist_id}","starred_url":"https://api.github.com/users/DHowett-MSFT/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DHowett-MSFT/subscriptions","organizations_url":"https://api.github.com/users/DHowett-MSFT/orgs","repos_url":"https://api.github.com/users/DHowett-MSFT/repos","events_url":"https://api.github.com/users/DHowett-MSFT/events{/privacy}","received_events_url":"https://api.github.com/users/DHowett-MSFT/received_events","type":"User","site_admin":false},"body":"You've introduced an observer information leak.\r\n\r\n```objc\r\n[object setValue:@\"x\" forKeyPath:@\"key\"]\r\n...\r\n[object addObserver:observer1 forKeyPath:@\"key\" options:/* prior, old, new */ context:0];\r\n[object addObserver:observer2 forKeyPath:@\"key\" options:/* prior */ context:0];\r\n...\r\n[object setValue:@\"y\" forKeyPath:@\"key\"]\r\n...\r\n// observer1 will receive old=\"x\"\r\n// observer2 will receive old=\"x\" as well, despite not requesting it.\r\n```","created_at":"2017-05-15T18:36:18Z","updated_at":"2017-05-15T18:36:28Z","html_url":"https://github.com/Microsoft/WinObjC/pull/2693#discussion_r116568059","pull_request_url":"https://api.github.com/repos/Microsoft/WinObjC/pulls/2693","_links":{"self":{"href":"https://api.github.com/repos/Microsoft/WinObjC/pulls/comments/116568059"},"html":{"href":"https://github.com/Microsoft/WinObjC/pull/2693#discussion_r116568059"},"pull_request":{"href":"https://api.github.com/repos/Microsoft/WinObjC/pulls/2693"}}},"pull_request":{"url":"https://api.github.com/repos/Microsoft/WinObjC/pulls/2693","id":120187345,"html_url":"https://github.com/Microsoft/WinObjC/pull/2693","diff_url":"https://github.com/Microsoft/WinObjC/pull/2693.diff","patch_url":"https://github.com/Microsoft/WinObjC/pull/2693.patch","issue_url":"https://api.github.com/repos/Microsoft/WinObjC/issues/2693","number":2693,"state":"open","locked":false,"title":"Implement set mutation KVO methods","user":{"login":"aballway","id":7557395,"avatar_url":"https://avatars0.githubusercontent.com/u/7557395?v=3","gravatar_id":"","url":"https://api.github.com/users/aballway","html_url":"https://github.com/aballway","followers_url":"https://api.github.com/users/aballway/followers","following_url":"https://api.github.com/users/aballway/following{/other_user}","gists_url":"https://api.github.com/users/aballway/gists{/gist_id}","starred_url":"https://api.github.com/users/aballway/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aballway/subscriptions","organizations_url":"https://api.github.com/users/aballway/orgs","repos_url":"https://api.github.com/users/aballway/repos","events_url":"https://api.github.com/users/aballway/events{/privacy}","received_events_url":"https://api.github.com/users/aballway/received_events","type":"User","site_admin":false},"body":"Implements NSObject willChangeValueForKey:withSetMutation:usingObjects: and didChangeValueForKey:withSetMutation:usingObjects:\r\n\r\nAlso does a small amount of clean-up\r\n\r\nFixes #2641\n\n<!-- Reviewable:start -->\n---\nThis change is [<img src=\"https://reviewable.io/review_button.svg\" height=\"34\" align=\"absmiddle\" alt=\"Reviewable\"/>](https://reviewable.io/reviews/microsoft/winobjc/2693)\n<!-- Reviewable:end -->\n","created_at":"2017-05-11T19:52:03Z","updated_at":"2017-05-15T18:36:28Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e303c01d019efb5551b792aa7b79bee63a3a18eb","assignee":null,"assignees":[],"requested_reviewers":[],"milestone":null,"commits_url":"https://api.github.com/repos/Microsoft/WinObjC/pulls/2693/commits","review_comments_url":"https://api.github.com/repos/Microsoft/WinObjC/pulls/2693/comments","review_comment_url":"https://api.github.com/repos/Microsoft/WinObjC/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Microsoft/WinObjC/issues/2693/comments","statuses_url":"https://api.github.com/repos/Microsoft/WinObjC/statuses/222696766ec66a7ec4571e9b6cdf729790c3b821","head":{"label":"aballway:nsobject","ref":"nsobject","sha":"222696766ec66a7ec4571e9b6cdf729790c3b821","user":{"login":"aballway","id":7557395,"avatar_url":"https://avatars0.githubusercontent.com/u/7557395?v=3","gravatar_id":"","url":"https://api.github.com/users/aballway","html_url":"https://github.com/aballway","followers_url":"https://api.github.com/users/aballway/followers","following_url":"https://api.github.com/users/aballway/following{/other_user}","gists_url":"https://api.github.com/users/aballway/gists{/gist_id}","starred_url":"https://api.github.com/users/aballway/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aballway/subscriptions","organizations_url":"https://api.github.com/users/aballway/orgs","repos_url":"https://api.github.com/users/aballway/repos","events_url":"https://api.github.com/users/aballway/events{/privacy}","received_events_url":"https://api.github.com/users/aballway/received_events","type":"User","site_admin":false},"repo":{"id":62087158,"name":"WinObjC","full_name":"aballway/WinObjC","owner":{"login":"aballway","id":7557395,"avatar_url":"https://avatars0.githubusercontent.com/u/7557395?v=3","gravatar_id":"","url":"https://api.github.com/users/aballway","html_url":"https://github.com/aballway","followers_url":"https://api.github.com/users/aballway/followers","following_url":"https://api.github.com/users/aballway/following{/other_user}","gists_url":"https://api.github.com/users/aballway/gists{/gist_id}","starred_url":"https://api.github.com/users/aballway/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aballway/subscriptions","organizations_url":"https://api.github.com/users/aballway/orgs","repos_url":"https://api.github.com/users/aballway/repos","events_url":"https://api.github.com/users/aballway/events{/privacy}","received_events_url":"https://api.github.com/users/aballway/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/aballway/WinObjC","description":"Objective-C for Windows","fork":true,"url":"https://api.github.com/repos/aballway/WinObjC","forks_url":"https://api.github.com/repos/aballway/WinObjC/forks","keys_url":"https://api.github.com/repos/aballway/WinObjC/keys{/key_id}","collaborators_url":"https://api.github.com/repos/aballway/WinObjC/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/aballway/WinObjC/teams","hooks_url":"https://api.github.com/repos/aballway/WinObjC/hooks","issue_events_url":"https://api.github.com/repos/aballway/WinObjC/issues/events{/number}","events_url":"https://api.github.com/repos/aballway/WinObjC/events","assignees_url":"https://api.github.com/repos/aballway/WinObjC/assignees{/user}","branches_url":"https://api.github.com/repos/aballway/WinObjC/branches{/branch}","tags_url":"https://api.github.com/repos/aballway/WinObjC/tags","blobs_url":"https://api.github.com/repos/aballway/WinObjC/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/aballway/WinObjC/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/aballway/WinObjC/git/refs{/sha}","trees_url":"https://api.github.com/repos/aballway/WinObjC/git/trees{/sha}","statuses_url":"https://api.github.com/repos/aballway/WinObjC/statuses/{sha}","languages_url":"https://api.github.com/repos/aballway/WinObjC/languages","stargazers_url":"https://api.github.com/repos/aballway/WinObjC/stargazers","contributors_url":"https://api.github.com/repos/aballway/WinObjC/contributors","subscribers_url":"https://api.github.com/repos/aballway/WinObjC/subscribers","subscription_url":"https://api.github.com/repos/aballway/WinObjC/subscription","commits_url":"https://api.github.com/repos/aballway/WinObjC/commits{/sha}","git_commits_url":"https://api.github.com/repos/aballway/WinObjC/git/commits{/sha}","comments_url":"https://api.github.com/repos/aballway/WinObjC/comments{/number}","issue_comment_url":"https://api.github.com/repos/aballway/WinObjC/issues/comments{/number}","contents_url":"https://api.github.com/repos/aballway/WinObjC/contents/{+path}","compare_url":"https://api.github.com/repos/aballway/WinObjC/compare/{base}...{head}","merges_url":"https://api.github.com/repos/aballway/WinObjC/merges","archive_url":"https://api.github.com/repos/aballway/WinObjC/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/aballway/WinObjC/downloads","issues_url":"https://api.github.com/repos/aballway/WinObjC/issues{/number}","pulls_url":"https://api.github.com/repos/aballway/WinObjC/pulls{/number}","milestones_url":"https://api.github.com/repos/aballway/WinObjC/milestones{/number}","notifications_url":"https://api.github.com/repos/aballway/WinObjC/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/aballway/WinObjC/labels{/name}","releases_url":"https://api.github.com/repos/aballway/WinObjC/releases{/id}","deployments_url":"https://api.github.com/repos/aballway/WinObjC/deployments","created_at":"2016-06-27T20:48:04Z","updated_at":"2016-11-11T16:51:37Z","pushed_at":"2017-05-15T18:20:52Z","git_url":"git://github.com/aballway/WinObjC.git","ssh_url":"git@github.com:aballway/WinObjC.git","clone_url":"https://github.com/aballway/WinObjC.git","svn_url":"https://github.com/aballway/WinObjC","homepage":"","size":343028,"stargazers_count":0,"watchers_count":0,"language":"Objective-C++","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"Microsoft:develop","ref":"develop","sha":"8df13fbb1f8a42400e942fc4762ac292342f9e0a","user":{"login":"Microsoft","id":6154722,"avatar_url":"https://avatars1.githubusercontent.com/u/6154722?v=3","gravatar_id":"","url":"https://api.github.com/users/Microsoft","html_url":"https://github.com/Microsoft","followers_url":"https://api.github.com/users/Microsoft/followers","following_url":"https://api.github.com/users/Microsoft/following{/other_user}","gists_url":"https://api.github.com/users/Microsoft/gists{/gist_id}","starred_url":"https://api.github.com/users/Microsoft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Microsoft/subscriptions","organizations_url":"https://api.github.com/users/Microsoft/orgs","repos_url":"https://api.github.com/users/Microsoft/repos","events_url":"https://api.github.com/users/Microsoft/events{/privacy}","received_events_url":"https://api.github.com/users/Microsoft/received_events","type":"Organization","site_admin":false},"repo":{"id":37950166,"name":"WinObjC","full_name":"Microsoft/WinObjC","owner":{"login":"Microsoft","id":6154722,"avatar_url":"https://avatars1.githubusercontent.com/u/6154722?v=3","gravatar_id":"","url":"https://api.github.com/users/Microsoft","html_url":"https://github.com/Microsoft","followers_url":"https://api.github.com/users/Microsoft/followers","following_url":"https://api.github.com/users/Microsoft/following{/other_user}","gists_url":"https://api.github.com/users/Microsoft/gists{/gist_id}","starred_url":"https://api.github.com/users/Microsoft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Microsoft/subscriptions","organizations_url":"https://api.github.com/users/Microsoft/orgs","repos_url":"https://api.github.com/users/Microsoft/repos","events_url":"https://api.github.com/users/Microsoft/events{/privacy}","received_events_url":"https://api.github.com/users/Microsoft/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Microsoft/WinObjC","description":"Objective-C for Windows","fork":false,"url":"https://api.github.com/repos/Microsoft/WinObjC","forks_url":"https://api.github.com/repos/Microsoft/WinObjC/forks","keys_url":"https://api.github.com/repos/Microsoft/WinObjC/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Microsoft/WinObjC/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Microsoft/WinObjC/teams","hooks_url":"https://api.github.com/repos/Microsoft/WinObjC/hooks","issue_events_url":"https://api.github.com/repos/Microsoft/WinObjC/issues/events{/number}","events_url":"https://api.github.com/repos/Microsoft/WinObjC/events","assignees_url":"https://api.github.com/repos/Microsoft/WinObjC/assignees{/user}","branches_url":"https://api.github.com/repos/Microsoft/WinObjC/branches{/branch}","tags_url":"https://api.github.com/repos/Microsoft/WinObjC/tags","blobs_url":"https://api.github.com/repos/Microsoft/WinObjC/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Microsoft/WinObjC/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Microsoft/WinObjC/git/refs{/sha}","trees_url":"https://api.github.com/repos/Microsoft/WinObjC/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Microsoft/WinObjC/statuses/{sha}","languages_url":"https://api.github.com/repos/Microsoft/WinObjC/languages","stargazers_url":"https://api.github.com/repos/Microsoft/WinObjC/stargazers","contributors_url":"https://api.github.com/repos/Microsoft/WinObjC/contributors","subscribers_url":"https://api.github.com/repos/Microsoft/WinObjC/subscribers","subscription_url":"https://api.github.com/repos/Microsoft/WinObjC/subscription","commits_url":"https://api.github.com/repos/Microsoft/WinObjC/commits{/sha}","git_commits_url":"https://api.github.com/repos/Microsoft/WinObjC/git/commits{/sha}","comments_url":"https://api.github.com/repos/Microsoft/WinObjC/comments{/number}","issue_comment_url":"https://api.github.com/repos/Microsoft/WinObjC/issues/comments{/number}","contents_url":"https://api.github.com/repos/Microsoft/WinObjC/contents/{+path}","compare_url":"https://api.github.com/repos/Microsoft/WinObjC/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Microsoft/WinObjC/merges","archive_url":"https://api.github.com/repos/Microsoft/WinObjC/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Microsoft/WinObjC/downloads","issues_url":"https://api.github.com/repos/Microsoft/WinObjC/issues{/number}","pulls_url":"https://api.github.com/repos/Microsoft/WinObjC/pulls{/number}","milestones_url":"https://api.github.com/repos/Microsoft/WinObjC/milestones{/number}","notifications_url":"https://api.github.com/repos/Microsoft/WinObjC/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Microsoft/WinObjC/labels{/name}","releases_url":"https://api.github.com/repos/Microsoft/WinObjC/releases{/id}","deployments_url":"https://api.github.com/repos/Microsoft/WinObjC/deployments","created_at":"2015-06-23T23:14:58Z","updated_at":"2017-05-14T18:40:53Z","pushed_at":"2017-05-15T17:32:22Z","git_url":"git://github.com/Microsoft/WinObjC.git","ssh_url":"git@github.com:Microsoft/WinObjC.git","clone_url":"https://github.com/Microsoft/WinObjC.git","svn_url":"https://github.com/Microsoft/WinObjC","homepage":"","size":383113,"stargazers_count":5699,"watchers_count":5699,"language":"Objective-C++","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":747,"mirror_url":null,"open_issues_count":349,"forks":747,"open_issues":349,"watchers":5699,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/Microsoft/WinObjC/pulls/2693"},"html":{"href":"https://github.com/Microsoft/WinObjC/pull/2693"},"issue":{"href":"https://api.github.com/repos/Microsoft/WinObjC/issues/2693"},"comments":{"href":"https://api.github.com/repos/Microsoft/WinObjC/issues/2693/comments"},"review_comments":{"href":"https://api.github.com/repos/Microsoft/WinObjC/pulls/2693/comments"},"review_comment":{"href":"https://api.github.com/repos/Microsoft/WinObjC/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Microsoft/WinObjC/pulls/2693/commits"},"statuses":{"href":"https://api.github.com/repos/Microsoft/WinObjC/statuses/222696766ec66a7ec4571e9b6cdf729790c3b821"}}}}
{"actor":{"display_login":"DHowett-MSFT"}}