[Cocoa] Adapt more WebKit code to be ARC-compatible
[WebKit-https.git] / Source / WebKit / Shared / API / Cocoa / _WKRemoteObjectInterface.mm
1 /*
2  * Copyright (C) 2013 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "_WKRemoteObjectInterfaceInternal.h"
28
29 #if WK_API_ENABLED
30
31 #import <objc/runtime.h>
32 #import <wtf/HashMap.h>
33 #import <wtf/NeverDestroyed.h>
34 #import <wtf/Optional.h>
35 #import <wtf/RetainPtr.h>
36 #import <wtf/Vector.h>
37 #import <wtf/text/CString.h>
38
39 extern "C"
40 const char *_protocol_getMethodTypeEncoding(Protocol *p, SEL sel, BOOL isRequiredMethod, BOOL isInstanceMethod);
41
42 @interface NSMethodSignature ()
43 - (NSMethodSignature *)_signatureForBlockAtArgumentIndex:(NSInteger)idx;
44 - (Class)_classForObjectAtArgumentIndex:(NSInteger)idx;
45 - (NSString *)_typeString;
46 @end
47
48 struct MethodInfo {
49     Vector<HashSet<CFTypeRef>> allowedArgumentClasses;
50
51     struct ReplyInfo {
52         NSUInteger replyPosition;
53         CString replySignature;
54         Vector<HashSet<CFTypeRef>> allowedReplyClasses;
55     };
56     std::optional<ReplyInfo> replyInfo;
57 };
58
59 @implementation _WKRemoteObjectInterface {
60     RetainPtr<NSString> _identifier;
61
62     HashMap<SEL, MethodInfo> _methods;
63 }
64
65 static bool isContainerClass(Class objectClass)
66 {
67     // FIXME: Add more classes here if needed.
68     static Class arrayClass = [NSArray class];
69     static Class dictionaryClass = [NSDictionary class];
70     return objectClass == arrayClass || objectClass == dictionaryClass;
71 }
72
73 static HashSet<CFTypeRef>& propertyListClasses()
74 {
75     static LazyNeverDestroyed<HashSet<CFTypeRef>> propertyListClasses;
76     static dispatch_once_t onceToken;
77     dispatch_once(&onceToken, ^{
78         propertyListClasses.construct(std::initializer_list<CFTypeRef> {
79             (__bridge CFTypeRef)[NSArray class], (__bridge CFTypeRef)[NSDictionary class],
80             (__bridge CFTypeRef)[NSNumber class], (__bridge CFTypeRef)[NSString class]
81         });
82     });
83
84     return propertyListClasses;
85 }
86
87 static void initializeMethod(MethodInfo& methodInfo, Protocol *protocol, SEL selector, NSMethodSignature *methodSignature, bool forReplyBlock)
88 {
89     Vector<HashSet<CFTypeRef>> allowedClasses;
90
91     NSUInteger firstArgument = forReplyBlock ? 1 : 2;
92     NSUInteger argumentCount = methodSignature.numberOfArguments;
93
94     bool foundBlock = false;
95     for (NSUInteger i = firstArgument; i < argumentCount; ++i) {
96         const char* argumentType = [methodSignature getArgumentTypeAtIndex:i];
97
98         if (*argumentType != '@') {
99             // This is a non-object type; we won't allow any classes to be decoded for it.
100             allowedClasses.append({ });
101             continue;
102         }
103
104         if (*(argumentType + 1) == '?') {
105             if (forReplyBlock)
106                 [NSException raise:NSInvalidArgumentException format:@"Blocks as arguments to the reply block of method (%s / %s) are not allowed", protocol_getName(protocol), sel_getName(selector)];
107
108             if (foundBlock)
109                 [NSException raise:NSInvalidArgumentException format:@"Only one reply block is allowed per method (%s / %s)", protocol_getName(protocol), sel_getName(selector)];
110             foundBlock = true;
111             NSMethodSignature *blockSignature = [methodSignature _signatureForBlockAtArgumentIndex:i];
112             ASSERT(blockSignature._typeString);
113
114             methodInfo.replyInfo = MethodInfo::ReplyInfo();
115             methodInfo.replyInfo->replyPosition = i;
116             methodInfo.replyInfo->replySignature = blockSignature._typeString.UTF8String;
117
118             initializeMethod(methodInfo, protocol, selector, blockSignature, true);
119         }
120
121         Class objectClass = [methodSignature _classForObjectAtArgumentIndex:i];
122         if (!objectClass) {
123             allowedClasses.append({ });
124             continue;
125         }
126
127         if (isContainerClass(objectClass)) {
128             // For container classes, we allow all simple property list classes.
129             allowedClasses.append(propertyListClasses());
130             continue;
131         }
132
133         allowedClasses.append({ (__bridge CFTypeRef)objectClass });
134     }
135
136     if (forReplyBlock)
137         methodInfo.replyInfo->allowedReplyClasses = WTFMove(allowedClasses);
138     else
139         methodInfo.allowedArgumentClasses = WTFMove(allowedClasses);
140 }
141
142 static void initializeMethods(_WKRemoteObjectInterface *interface, Protocol *protocol, bool requiredMethods)
143 {
144     unsigned methodCount;
145     struct objc_method_description *methods = protocol_copyMethodDescriptionList(protocol, requiredMethods, true, &methodCount);
146
147     for (unsigned i = 0; i < methodCount; ++i) {
148         SEL selector = methods[i].name;
149
150         ASSERT(!interface->_methods.contains(selector));
151         MethodInfo& methodInfo = interface->_methods.add(selector, MethodInfo()).iterator->value;
152
153         const char* methodTypeEncoding = _protocol_getMethodTypeEncoding(protocol, selector, requiredMethods, true);
154         if (!methodTypeEncoding)
155             [NSException raise:NSInvalidArgumentException format:@"Could not find method type encoding for method \"%s\"", sel_getName(selector)];
156
157         NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:methodTypeEncoding];
158
159         initializeMethod(methodInfo, protocol, selector, methodSignature, false);
160     }
161
162     free(methods);
163 }
164
165 static void initializeMethods(_WKRemoteObjectInterface *interface, Protocol *protocol)
166 {
167     unsigned conformingProtocolCount;
168     auto conformingProtocols = protocol_copyProtocolList(protocol, &conformingProtocolCount);
169
170     for (unsigned i = 0; i < conformingProtocolCount; ++i) {
171         auto conformingProtocol = conformingProtocols[i];
172         if (conformingProtocol == @protocol(NSObject))
173             continue;
174         initializeMethods(interface, conformingProtocol);
175     }
176
177     free(conformingProtocols);
178
179     initializeMethods(interface, protocol, true);
180     initializeMethods(interface, protocol, false);
181 }
182
183 - (id)initWithProtocol:(Protocol *)protocol identifier:(NSString *)identifier
184 {
185     if (!(self = [super init]))
186         return nil;
187
188     _protocol = protocol;
189     _identifier = adoptNS([identifier copy]);
190
191     initializeMethods(self, _protocol);
192
193     return self;
194 }
195
196 + (instancetype)remoteObjectInterfaceWithProtocol:(Protocol *)protocol
197 {
198     return [[[self alloc] initWithProtocol:protocol identifier:NSStringFromProtocol(protocol)] autorelease];
199 }
200
201 - (NSString *)identifier
202 {
203     return _identifier.get();
204 }
205
206 - (NSString *)debugDescription
207 {
208     auto result = adoptNS([[NSMutableString alloc] initWithFormat:@"<%@: %p; protocol = \"%@\"; identifier = \"%@\"\n", NSStringFromClass(self.class), self, _identifier.get(), NSStringFromProtocol(_protocol)]);
209
210     auto descriptionForClasses = [](auto& allowedClasses) {
211         auto result = adoptNS([[NSMutableString alloc] initWithString:@"["]);
212         bool needsComma = false;
213
214         auto descriptionForArgument = [](auto& allowedArgumentClasses) {
215             auto result = adoptNS([[NSMutableString alloc] initWithString:@"{"]);
216
217             auto orderedArgumentClasses = copyToVector(allowedArgumentClasses);
218             std::sort(orderedArgumentClasses.begin(), orderedArgumentClasses.end(), [](CFTypeRef a, CFTypeRef b) {
219                 return CString(class_getName((__bridge Class)a)) < CString(class_getName((__bridge Class)b));
220             });
221
222             bool needsComma = false;
223             for (auto& argumentClass : orderedArgumentClasses) {
224                 if (needsComma)
225                     [result appendString:@", "];
226
227                 [result appendFormat:@"%s", class_getName((__bridge Class)argumentClass)];
228                 needsComma = true;
229             }
230
231             [result appendString:@"}"];
232             return result.autorelease();
233         };
234
235         for (auto& argumentClasses : allowedClasses) {
236             if (needsComma)
237                 [result appendString:@", "];
238
239             [result appendString:descriptionForArgument(argumentClasses)];
240             needsComma = true;
241         }
242
243         [result appendString:@"]"];
244         return result.autorelease();
245     };
246
247     for (auto& selectorAndMethod : _methods) {
248         [result appendFormat:@" selector = %s\n  argument classes = %@\n", sel_getName(selectorAndMethod.key), descriptionForClasses(selectorAndMethod.value.allowedArgumentClasses)];
249
250         if (auto replyInfo = selectorAndMethod.value.replyInfo)
251             [result appendFormat:@"  reply block = (argument #%lu '%s') %@\n", static_cast<unsigned long>(replyInfo->replyPosition), replyInfo->replySignature.data(), descriptionForClasses(replyInfo->allowedReplyClasses)];
252     }
253
254     [result appendString:@">\n"];
255     return result.autorelease();
256 }
257
258 static HashSet<CFTypeRef>& classesForSelectorArgument(_WKRemoteObjectInterface *interface, SEL selector, NSUInteger argumentIndex, bool replyBlock)
259 {
260     auto it = interface->_methods.find(selector);
261     if (it == interface->_methods.end())
262         [NSException raise:NSInvalidArgumentException format:@"Interface does not contain selector \"%s\"", sel_getName(selector)];
263
264     MethodInfo& methodInfo = it->value;
265     if (replyBlock) {
266         if (!methodInfo.replyInfo)
267             [NSException raise:NSInvalidArgumentException format:@"Selector \"%s\" does not have a reply block", sel_getName(selector)];
268
269         if (argumentIndex >= methodInfo.replyInfo->allowedReplyClasses.size())
270             [NSException raise:NSInvalidArgumentException format:@"Argument index %ld is out of range for reply block of selector \"%s\"", (unsigned long)argumentIndex, sel_getName(selector)];
271
272         return methodInfo.replyInfo->allowedReplyClasses[argumentIndex];
273     }
274
275     if (argumentIndex >= methodInfo.allowedArgumentClasses.size())
276         [NSException raise:NSInvalidArgumentException format:@"Argument index %ld is out of range for selector \"%s\"", (unsigned long)argumentIndex, sel_getName(selector)];
277
278     return methodInfo.allowedArgumentClasses[argumentIndex];
279 }
280
281 - (NSSet *)classesForSelector:(SEL)selector argumentIndex:(NSUInteger)argumentIndex ofReply:(BOOL)ofReply
282 {
283     auto result = adoptNS([[NSMutableSet alloc] init]);
284
285     for (auto allowedClass : classesForSelectorArgument(self, selector, argumentIndex, ofReply))
286         [result addObject:(__bridge Class)allowedClass];
287
288     return result.autorelease();
289 }
290
291 - (void)setClasses:(NSSet *)classes forSelector:(SEL)selector argumentIndex:(NSUInteger)argumentIndex ofReply:(BOOL)ofReply
292 {
293     HashSet<CFTypeRef> allowedClasses;
294     for (Class allowedClass in classes)
295         allowedClasses.add((__bridge CFTypeRef)allowedClass);
296
297     classesForSelectorArgument(self, selector, argumentIndex, ofReply) = WTFMove(allowedClasses);
298 }
299
300 - (NSSet *)classesForSelector:(SEL)selector argumentIndex:(NSUInteger)argumentIndex
301 {
302     return [self classesForSelector:selector argumentIndex:argumentIndex ofReply:NO];
303 }
304
305 - (void)setClasses:(NSSet *)classes forSelector:(SEL)selector argumentIndex:(NSUInteger)argumentIndex
306 {
307     [self setClasses:classes forSelector:selector argumentIndex:argumentIndex ofReply:NO];
308 }
309
310 static const char* methodArgumentTypeEncodingForSelector(Protocol *protocol, SEL selector)
311 {
312     // First look at required methods.
313     struct objc_method_description method = protocol_getMethodDescription(protocol, selector, YES, YES);
314     if (method.name)
315         return method.types;
316
317     // Then look at optional methods.
318     method = protocol_getMethodDescription(protocol, selector, NO, YES);
319     if (method.name)
320         return method.types;
321
322     return nullptr;
323 }
324
325 - (NSMethodSignature *)_methodSignatureForSelector:(SEL)selector
326 {
327     if (!_methods.contains(selector))
328         return nil;
329
330     const char* types = methodArgumentTypeEncodingForSelector(_protocol, selector);
331     if (!types)
332         return nil;
333
334     return [NSMethodSignature signatureWithObjCTypes:types];
335 }
336
337 - (NSMethodSignature *)_methodSignatureForReplyBlockOfSelector:(SEL)selector
338 {
339     auto it = _methods.find(selector);
340     if (it  == _methods.end())
341         return nil;
342
343     auto& methodInfo = it->value;
344     if (!methodInfo.replyInfo)
345         return nil;
346
347     return [NSMethodSignature signatureWithObjCTypes:methodInfo.replyInfo->replySignature.data()];
348 }
349
350 - (const Vector<HashSet<CFTypeRef>>&)_allowedArgumentClassesForSelector:(SEL)selector
351 {
352     ASSERT(_methods.contains(selector));
353
354     return _methods.find(selector)->value.allowedArgumentClasses;
355 }
356
357 - (const Vector<HashSet<CFTypeRef>>&)_allowedArgumentClassesForReplyBlockOfSelector:(SEL)selector
358 {
359     ASSERT(_methods.contains(selector));
360
361     return _methods.find(selector)->value.replyInfo->allowedReplyClasses;
362 }
363
364 @end
365
366 #endif // WK_API_ENABLED