d8f0f1cf50785665d1d8da8f89bbdfc3f89a3083
[WebKit-https.git] / Source / WebKit2 / 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<Class>> allowedArgumentClasses;
50
51     struct ReplyInfo {
52         NSUInteger replyPosition;
53         CString replySignature;
54         Vector<HashSet<Class>> allowedReplyClasses;
55     };
56     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 LazyNeverDestroyed<HashSet<Class>> containerClasses;
69     static dispatch_once_t onceToken;
70     dispatch_once(&onceToken, ^{
71         containerClasses.construct(std::initializer_list<Class> { [NSArray class], [NSDictionary class] });
72     });
73
74     return containerClasses->contains(objectClass);
75 }
76
77 static HashSet<Class>& propertyListClasses()
78 {
79     static LazyNeverDestroyed<HashSet<Class>> propertyListClasses;
80     static dispatch_once_t onceToken;
81     dispatch_once(&onceToken, ^{
82         propertyListClasses.construct(std::initializer_list<Class> { [NSArray class], [NSDictionary class], [NSNumber class], [NSString class] });
83     });
84
85     return propertyListClasses;
86 }
87
88 static void initializeMethod(MethodInfo& methodInfo, Protocol *protocol, SEL selector, NSMethodSignature *methodSignature, bool forReplyBlock)
89 {
90     Vector<HashSet<Class>> allowedClasses;
91
92     NSUInteger firstArgument = forReplyBlock ? 1 : 2;
93     NSUInteger argumentCount = methodSignature.numberOfArguments;
94
95     bool foundBlock = false;
96     for (NSUInteger i = firstArgument; i < argumentCount; ++i) {
97         const char* argumentType = [methodSignature getArgumentTypeAtIndex:i];
98
99         if (*argumentType != '@') {
100             // This is a non-object type; we won't allow any classes to be decoded for it.
101             allowedClasses.append({ });
102             continue;
103         }
104
105         if (*(argumentType + 1) == '?') {
106             if (forReplyBlock)
107                 [NSException raise:NSInvalidArgumentException format:@"Blocks as arguments to the reply block of method (%s / %s) are not allowed", protocol_getName(protocol), sel_getName(selector)];
108
109             if (foundBlock)
110                 [NSException raise:NSInvalidArgumentException format:@"Only one reply block is allowed per method (%s / %s)", protocol_getName(protocol), sel_getName(selector)];
111             foundBlock = true;
112             NSMethodSignature *blockSignature = [methodSignature _signatureForBlockAtArgumentIndex:i];
113             ASSERT(blockSignature._typeString);
114
115             methodInfo.replyInfo = MethodInfo::ReplyInfo();
116             methodInfo.replyInfo->replyPosition = i;
117             methodInfo.replyInfo->replySignature = blockSignature._typeString.UTF8String;
118
119             initializeMethod(methodInfo, protocol, selector, blockSignature, true);
120         }
121
122         Class objectClass = [methodSignature _classForObjectAtArgumentIndex:i];
123         if (!objectClass) {
124             allowedClasses.append({ });
125             continue;
126         }
127
128         if (isContainerClass(objectClass)) {
129             // For container classes, we allow all simple property list classes.
130             allowedClasses.append(propertyListClasses());
131             continue;
132         }
133
134         allowedClasses.append({ objectClass });
135     }
136
137     if (forReplyBlock)
138         methodInfo.replyInfo->allowedReplyClasses = WTFMove(allowedClasses);
139     else
140         methodInfo.allowedArgumentClasses = WTFMove(allowedClasses);
141 }
142
143 static void initializeMethods(_WKRemoteObjectInterface *interface, Protocol *protocol, bool requiredMethods)
144 {
145     unsigned methodCount;
146     struct objc_method_description *methods = protocol_copyMethodDescriptionList(protocol, requiredMethods, true, &methodCount);
147
148     for (unsigned i = 0; i < methodCount; ++i) {
149         SEL selector = methods[i].name;
150
151         ASSERT(!interface->_methods.contains(selector));
152         MethodInfo& methodInfo = interface->_methods.add(selector, MethodInfo()).iterator->value;
153
154         const char* methodTypeEncoding = _protocol_getMethodTypeEncoding(protocol, selector, requiredMethods, true);
155         if (!methodTypeEncoding)
156             [NSException raise:NSInvalidArgumentException format:@"Could not find method type encoding for method \"%s\"", sel_getName(selector)];
157
158         NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:methodTypeEncoding];
159
160         initializeMethod(methodInfo, protocol, selector, methodSignature, false);
161     }
162
163     free(methods);
164 }
165
166 static void initializeMethods(_WKRemoteObjectInterface *interface, Protocol *protocol)
167 {
168     unsigned conformingProtocolCount;
169     Protocol** conformingProtocols = protocol_copyProtocolList(interface->_protocol, &conformingProtocolCount);
170
171     for (unsigned i = 0; i < conformingProtocolCount; ++i) {
172         Protocol* conformingProtocol = conformingProtocols[i];
173
174         if (conformingProtocol == @protocol(NSObject))
175             continue;
176
177         initializeMethods(interface, conformingProtocol);
178     }
179
180     free(conformingProtocols);
181
182     initializeMethods(interface, protocol, true);
183     initializeMethods(interface, protocol, false);
184 }
185
186 - (id)initWithProtocol:(Protocol *)protocol identifier:(NSString *)identifier
187 {
188     if (!(self = [super init]))
189         return nil;
190
191     _protocol = protocol;
192     _identifier = adoptNS([identifier copy]);
193
194     initializeMethods(self, _protocol);
195
196     return self;
197 }
198
199 + (instancetype)remoteObjectInterfaceWithProtocol:(Protocol *)protocol
200 {
201     return [[[self alloc] initWithProtocol:protocol identifier:NSStringFromProtocol(protocol)] autorelease];
202 }
203
204 - (NSString *)identifier
205 {
206     return _identifier.get();
207 }
208
209 - (NSString *)debugDescription
210 {
211     auto result = adoptNS([[NSMutableString alloc] initWithFormat:@"<%@: %p; protocol = \"%@\"; identifier = \"%@\"\n", NSStringFromClass(self.class), self, _identifier.get(), NSStringFromProtocol(_protocol)]);
212
213     auto descriptionForClasses = [](const Vector<HashSet<Class>>& allowedClasses) {
214         auto result = adoptNS([[NSMutableString alloc] initWithString:@"["]);
215         bool needsComma = false;
216
217         auto descriptionForArgument = [](const HashSet<Class>& allowedArgumentClasses) {
218             auto result = adoptNS([[NSMutableString alloc] initWithString:@"{"]);
219
220             Vector<Class> orderedArgumentClasses;
221             copyToVector(allowedArgumentClasses, orderedArgumentClasses);
222
223             std::sort(orderedArgumentClasses.begin(), orderedArgumentClasses.end(), [](Class a, Class b) {
224                 return CString(class_getName(a)) < CString(class_getName(b));
225             });
226
227             bool needsComma = false;
228             for (auto& argumentClass : orderedArgumentClasses) {
229                 if (needsComma)
230                     [result appendString:@", "];
231
232                 [result appendFormat:@"%s", class_getName(argumentClass)];
233                 needsComma = true;
234             }
235
236             [result appendString:@"}"];
237             return result.autorelease();
238         };
239
240         for (auto& argumentClasses : allowedClasses) {
241             if (needsComma)
242                 [result appendString:@", "];
243
244             [result appendString:descriptionForArgument(argumentClasses)];
245             needsComma = true;
246         }
247
248         [result appendString:@"]"];
249         return result.autorelease();
250     };
251
252     for (auto& selectorAndMethod : _methods) {
253         [result appendFormat:@" selector = %s\n  argument classes = %@\n", sel_getName(selectorAndMethod.key), descriptionForClasses(selectorAndMethod.value.allowedArgumentClasses)];
254
255         if (auto replyInfo = selectorAndMethod.value.replyInfo)
256             [result appendFormat:@"  reply block = (argument #%lu '%s') %@\n", static_cast<unsigned long>(replyInfo->replyPosition), replyInfo->replySignature.data(), descriptionForClasses(replyInfo->allowedReplyClasses)];
257     }
258
259     [result appendString:@">\n"];
260     return result.autorelease();
261 }
262
263 static HashSet<Class>& classesForSelectorArgument(_WKRemoteObjectInterface *interface, SEL selector, NSUInteger argumentIndex, bool replyBlock)
264 {
265     auto it = interface->_methods.find(selector);
266     if (it == interface->_methods.end())
267         [NSException raise:NSInvalidArgumentException format:@"Interface does not contain selector \"%s\"", sel_getName(selector)];
268
269     MethodInfo& methodInfo = it->value;
270     if (replyBlock) {
271         if (!methodInfo.replyInfo)
272             [NSException raise:NSInvalidArgumentException format:@"Selector \"%s\" does not have a reply block", sel_getName(selector)];
273
274         if (argumentIndex >= methodInfo.replyInfo->allowedReplyClasses.size())
275             [NSException raise:NSInvalidArgumentException format:@"Argument index %ld is out of range for reply block of selector \"%s\"", (unsigned long)argumentIndex, sel_getName(selector)];
276
277         return methodInfo.replyInfo->allowedReplyClasses[argumentIndex];
278     }
279
280     if (argumentIndex >= methodInfo.allowedArgumentClasses.size())
281         [NSException raise:NSInvalidArgumentException format:@"Argument index %ld is out of range for selector \"%s\"", (unsigned long)argumentIndex, sel_getName(selector)];
282
283     return methodInfo.allowedArgumentClasses[argumentIndex];
284 }
285
286 - (NSSet *)classesForSelector:(SEL)selector argumentIndex:(NSUInteger)argumentIndex ofReply:(BOOL)ofReply
287 {
288     auto result = adoptNS([[NSMutableSet alloc] init]);
289
290     for (auto allowedClass : classesForSelectorArgument(self, selector, argumentIndex, ofReply))
291         [result addObject:allowedClass];
292
293     return result.autorelease();
294 }
295
296 - (void)setClasses:(NSSet *)classes forSelector:(SEL)selector argumentIndex:(NSUInteger)argumentIndex ofReply:(BOOL)ofReply
297 {
298     HashSet<Class> allowedClasses;
299     for (Class allowedClass in classes)
300         allowedClasses.add(allowedClass);
301
302     classesForSelectorArgument(self, selector, argumentIndex, ofReply) = WTFMove(allowedClasses);
303 }
304
305 - (NSSet *)classesForSelector:(SEL)selector argumentIndex:(NSUInteger)argumentIndex
306 {
307     return [self classesForSelector:selector argumentIndex:argumentIndex ofReply:NO];
308 }
309
310 - (void)setClasses:(NSSet *)classes forSelector:(SEL)selector argumentIndex:(NSUInteger)argumentIndex
311 {
312     [self setClasses:classes forSelector:selector argumentIndex:argumentIndex ofReply:NO];
313 }
314
315 static const char* methodArgumentTypeEncodingForSelector(Protocol *protocol, SEL selector)
316 {
317     // First look at required methods.
318     struct objc_method_description method = protocol_getMethodDescription(protocol, selector, YES, YES);
319     if (method.name)
320         return method.types;
321
322     // Then look at optional methods.
323     method = protocol_getMethodDescription(protocol, selector, NO, YES);
324     if (method.name)
325         return method.types;
326
327     return nullptr;
328 }
329
330 - (NSMethodSignature *)_methodSignatureForSelector:(SEL)selector
331 {
332     if (!_methods.contains(selector))
333         return nil;
334
335     const char* types = methodArgumentTypeEncodingForSelector(_protocol, selector);
336     if (!types)
337         return nil;
338
339     return [NSMethodSignature signatureWithObjCTypes:types];
340 }
341
342 - (NSMethodSignature *)_methodSignatureForReplyBlockOfSelector:(SEL)selector
343 {
344     auto it = _methods.find(selector);
345     if (it  == _methods.end())
346         return nil;
347
348     auto& methodInfo = it->value;
349     if (!methodInfo.replyInfo)
350         return nil;
351
352     return [NSMethodSignature signatureWithObjCTypes:methodInfo.replyInfo->replySignature.data()];
353 }
354
355 - (const Vector<HashSet<Class>>&)_allowedArgumentClassesForSelector:(SEL)selector
356 {
357     ASSERT(_methods.contains(selector));
358
359     return _methods.find(selector)->value.allowedArgumentClasses;
360 }
361
362 - (const Vector<HashSet<Class>>&)_allowedArgumentClassesForReplyBlockOfSelector:(SEL)selector
363 {
364     ASSERT(_methods.contains(selector));
365
366     return _methods.find(selector)->value.replyInfo->allowedReplyClasses;
367 }
368
369 @end
370
371 #endif // WK_API_ENABLED