Changeset 273141 in webkit
- Timestamp:
- Feb 19, 2021 9:29:11 AM (3 years ago)
- Location:
- trunk
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebKit/ChangeLog
r273139 r273141 1 2021-02-19 Chris Dumez <cdumez@apple.com> 2 3 Norton Safe Web extension is causing crashes / hangs under [WKRemoteObjectEncoder encodeObject:forKey:] 4 https://bugs.webkit.org/show_bug.cgi?id=222172 5 6 Reviewed by Alex Christensen. 7 8 The extension appears to be trying to send a JSValue that is a DOM Node. WebKit makes the following 9 call to convert it into a NSDictionary: 10 `[[JSValue valueWithJSValueRef:value inContext:[JSContext contextWithJSGlobalContextRef:JSContextGetGlobalContext(context)]] toObject]` 11 12 JSC very aggressively iterates over all of the properties of the DOM Node and recursively ends up 13 converting the whole DOM tree with all their properties. This leads to a lot of cycles to as JSC 14 maintains the JSObject <-> NSObject identity during the conversion (Each time the JSDocument is 15 serialized, the same NSDictionary* pointer is used to represent it). 16 17 The logic introduced in r270559 to detect cycles was flawed because it relied on a NSSet of 18 NSObject* and [NSSet containsObject:] to detect the cycles. The issue is that [NSSet containsObject:] 19 doesn't do a simple pointer comparison but instead calls [NSObject isEqual:] which is very 20 expensive for types like NSDictionary and leads to trouble when the dictionary contains a cycle. 21 To address this I replaced the NSSet with a WTF::HashSet<NSObject *> so that key lookup ends up 22 doing a simple pointer comparison. 23 24 Even after the previous fix, the extension would still cause massive hangs because it would take 25 a very long time to try and encode the whole DOM tree with all the properties of each Node (even 26 without cycles). To address this, we now abort encoding when detecting a cycle instead of encoding 27 an empty object to break the cycle. 28 29 After this change, Safari becomes usable with this extension again. However, there are still much 30 shorter hangs that occur due to the converting of the JSNode into a JSDictionary via 31 [JSValue toObject]. We should probably improve this in a follow-up. 32 33 Easy way to reproduce the crash / hang: 34 1. Install Norton Safe Web & Norton Password Manager extension (may require a subscription) 35 2. Make sure the extensions are activated and turned on by clicking on their icons next to the 36 URL bar 37 3. Go to https://bugs.webkit.org/attachment.cgi?id=420530&action=edit 38 4. Click on the combo box next to "Review" -> Hang / Crash 39 40 No new tests, covered by WebKit.RemoteObjectRegistry API test. 41 42 * Shared/API/Cocoa/WKRemoteObjectCoder.mm: 43 (-[WKRemoteObjectEncoder init]): 44 (encodeInvocationArguments): 45 (encodeObject): 46 1 47 2021-02-19 Kimmo Kinnunen <kkinnunen@apple.com> 2 48 -
trunk/Source/WebKit/Shared/API/Cocoa/WKRemoteObjectCoder.mm
r272936 r273141 64 64 65 65 API::Dictionary* _currentDictionary; 66 RetainPtr<NSMutableSet> _objectsBeingEncoded; // Used to detect cycles.66 HashSet<NSObject *> _objectsBeingEncoded; // Used to detect cycles. 67 67 } 68 68 … … 74 74 _rootDictionary = API::Dictionary::create(); 75 75 _currentDictionary = _rootDictionary.get(); 76 _objectsBeingEncoded = adoptNS([[NSMutableSet alloc] init]);77 76 78 77 return self; … … 249 248 [invocation getArgument:&value atIndex:i]; 250 249 251 encodeToObjectStream(encoder, value); 250 @try { 251 encodeToObjectStream(encoder, value); 252 } @catch (NSException *e) { 253 RELEASE_LOG_ERROR(IPC, "WKRemoteObjectCode::encodeInvocationArguments: Exception caught when trying to encode an argument of type ObjC Object"); 254 } 255 252 256 break; 253 257 } … … 310 314 [NSException raise:NSInvalidArgumentException format:@"-classForCoder returned nil for %@", object]; 311 315 312 if ( [encoder->_objectsBeingEncoded containsObject:object]) {316 if (encoder->_objectsBeingEncoded.contains(object)) { 313 317 RELEASE_LOG_FAULT(IPC, "WKRemoteObjectCode::encodeObject: Object of type '%{private}s' contains a cycle", class_getName(object_getClass(object))); 314 @try { 315 // Try to encode a newly initialized object instead. 316 object = adoptNS([[[object class] alloc] init]).autorelease(); 317 } @catch (NSException *e) { 318 [NSException raise:NSInvalidArgumentException format:@"Object of type '%s' contains a cycle", class_getName(object_getClass(object))]; 319 } 320 } 321 322 [encoder->_objectsBeingEncoded addObject:object]; 318 [NSException raise:NSInvalidArgumentException format:@"Object of type '%s' contains a cycle", class_getName(object_getClass(object))]; 319 return; 320 } 321 322 encoder->_objectsBeingEncoded.add(object); 323 323 auto exitScope = makeScopeExit([encoder, object] { 324 [encoder->_objectsBeingEncoded removeObject:object];324 encoder->_objectsBeingEncoded.remove(object); 325 325 }); 326 326 -
trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/RemoteObjectRegistry.mm
r273062 r273141 151 151 @try { 152 152 [object takeDictionary:dictionaryWithCycle completionHandler:^(NSDictionary* value) { 153 NSString *name = [value objectForKey:@"name"]; 154 EXPECT_WK_STREQ(@"root", name); 155 NSDictionary *child = [value objectForKey:@"child"]; 156 EXPECT_TRUE(!!child); 157 NSString* childName = [child objectForKey:@"name"]; 158 EXPECT_WK_STREQ(@"foo", childName); 159 NSNumber *childValue = [child objectForKey:@"value"]; 160 EXPECT_EQ(1, [childValue integerValue]); 161 // We should have encoded parent as an empty NSDictionary. 162 NSDictionary *childParent = [child objectForKey:@"parent"]; 163 EXPECT_TRUE(!!childParent); 164 EXPECT_EQ(0U, [childParent count]); 153 EXPECT_TRUE(!value.count); 165 154 isDone = true; 166 155 }];
Note: See TracChangeset
for help on using the changeset viewer.