Rework the way allowed argument classes are stored
[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/RetainPtr.h>
35 #import <wtf/Vector.h>
36
37 extern "C"
38 const char *_protocol_getMethodTypeEncoding(Protocol *p, SEL sel, BOOL isRequiredMethod, BOOL isInstanceMethod);
39
40 @interface NSMethodSignature (WKDetails)
41 - (Class)_classForObjectAtArgumentIndex:(NSInteger)idx;
42 @end
43
44 struct MethodInfo {
45     Vector<HashSet<Class>> allowedArgumentClasses;
46
47     // FIXME: This should have allowed reply argument classes too.
48 };
49
50 @implementation _WKRemoteObjectInterface {
51     RetainPtr<NSString> _identifier;
52
53     HashMap<SEL, MethodInfo> _methods;
54 }
55
56 static bool isContainerClass(Class objectClass)
57 {
58     // FIXME: Add more classes here if needed.
59     static LazyNeverDestroyed<HashSet<Class>> containerClasses;
60     static dispatch_once_t onceToken;
61     dispatch_once(&onceToken, ^{
62         containerClasses.construct(std::initializer_list<Class> { [NSArray class], [NSDictionary class] });
63     });
64
65     return containerClasses->contains(objectClass);
66 }
67
68 static HashSet<Class>& propertyListClasses()
69 {
70     static LazyNeverDestroyed<HashSet<Class>> propertyListClasses;
71     static dispatch_once_t onceToken;
72     dispatch_once(&onceToken, ^{
73         propertyListClasses.construct(std::initializer_list<Class> { [NSArray class], [NSDictionary class], [NSNumber class], [NSString class] });
74     });
75
76     return propertyListClasses;
77 }
78
79 static void initializeMethod(MethodInfo& methodInfo, NSMethodSignature *methodSignature, bool forReplyBlock)
80 {
81     Vector<HashSet<Class>> allowedClasses;
82
83     NSUInteger firstArgument = forReplyBlock ? 1 : 2;
84     NSUInteger argumentCount = methodSignature.numberOfArguments;
85
86     for (NSUInteger i = firstArgument; i < argumentCount; ++i) {
87         const char* argumentType = [methodSignature getArgumentTypeAtIndex:i];
88
89         if (*argumentType != '@') {
90             // This is a non-object type; we won't allow any classes to be decoded for it.
91             allowedClasses.uncheckedAppend({ });
92             continue;
93         }
94
95         Class objectClass = [methodSignature _classForObjectAtArgumentIndex:i];
96         if (!objectClass) {
97             allowedClasses.append({ });
98             continue;
99         }
100
101         if (isContainerClass(objectClass)) {
102             // For container classes, we allow all simple property list classes.
103             allowedClasses.append(propertyListClasses());
104             continue;
105         }
106
107         allowedClasses.append({ objectClass });
108     }
109
110     methodInfo.allowedArgumentClasses = WTF::move(allowedClasses);
111 }
112
113 static void initializeMethods(_WKRemoteObjectInterface *interface, Protocol *protocol, bool requiredMethods)
114 {
115     unsigned methodCount;
116     struct objc_method_description *methods = protocol_copyMethodDescriptionList(protocol, requiredMethods, true, &methodCount);
117
118     for (unsigned i = 0; i < methodCount; ++i) {
119         SEL selector = methods[i].name;
120
121         ASSERT(!interface->_methods.contains(selector));
122         MethodInfo& methodInfo = interface->_methods.add(selector, MethodInfo()).iterator->value;
123
124         const char* methodTypeEncoding = _protocol_getMethodTypeEncoding(protocol, selector, requiredMethods, true);
125         if (!methodTypeEncoding)
126             [NSException raise:NSInvalidArgumentException format:@"Could not find method type encoding for method \"%s\"", sel_getName(selector)];
127
128         NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:methodTypeEncoding];
129
130         initializeMethod(methodInfo, methodSignature, false);
131     }
132
133     free(methods);
134 }
135
136 static void initializeMethods(_WKRemoteObjectInterface *interface, Protocol *protocol)
137 {
138     unsigned conformingProtocolCount;
139     Protocol** conformingProtocols = protocol_copyProtocolList(interface->_protocol, &conformingProtocolCount);
140
141     for (unsigned i = 0; i < conformingProtocolCount; ++i) {
142         Protocol* conformingProtocol = conformingProtocols[i];
143
144         if (conformingProtocol == @protocol(NSObject))
145             continue;
146
147         initializeMethods(interface, conformingProtocol);
148     }
149
150     free(conformingProtocols);
151
152     initializeMethods(interface, protocol, true);
153     initializeMethods(interface, protocol, false);
154 }
155
156 - (id)initWithProtocol:(Protocol *)protocol identifier:(NSString *)identifier
157 {
158     if (!(self = [super init]))
159         return nil;
160
161     _protocol = protocol;
162     _identifier = adoptNS([identifier copy]);
163
164     initializeMethods(self, _protocol);
165
166     return self;
167 }
168
169 + (instancetype)remoteObjectInterfaceWithProtocol:(Protocol *)protocol
170 {
171     return [[[self alloc] initWithProtocol:protocol identifier:NSStringFromProtocol(protocol)] autorelease];
172 }
173
174 - (NSString *)identifier
175 {
176     return _identifier.get();
177 }
178
179 - (NSString *)description
180 {
181     return [NSString stringWithFormat:@"<%@: %p; protocol = \"%@\"; identifier = \"%@\">", NSStringFromClass(self.class), self, _identifier.get(), NSStringFromProtocol(_protocol)];
182 }
183
184 static HashSet<Class>& classesForSelectorArgument(_WKRemoteObjectInterface *interface, SEL selector, NSUInteger argumentIndex)
185 {
186     auto it = interface->_methods.find(selector);
187     if (it == interface->_methods.end())
188         [NSException raise:NSInvalidArgumentException format:@"Interface does not contain selector \"%s\"", sel_getName(selector)];
189
190     MethodInfo& methodInfo = it->value;
191     auto& allowedArgumentClasses = methodInfo.allowedArgumentClasses;
192
193     if (argumentIndex >= allowedArgumentClasses.size())
194         [NSException raise:NSInvalidArgumentException format:@"Argument index %ld is out of range for selector \"%s\"", (unsigned long)argumentIndex, sel_getName(selector)];
195
196     return allowedArgumentClasses[argumentIndex];
197 }
198
199 - (NSSet *)classesForSelector:(SEL)selector argumentIndex:(NSUInteger)argumentIndex
200 {
201     auto result = adoptNS([[NSMutableSet alloc] init]);
202
203     for (auto allowedClass : classesForSelectorArgument(self, selector, argumentIndex))
204         [result addObject:allowedClass];
205
206     return result.autorelease();
207 }
208
209 - (void)setClasses:(NSSet *)classes forSelector:(SEL)selector argumentIndex:(NSUInteger)argumentIndex
210 {
211     HashSet<Class> allowedClasses;
212     for (Class allowedClass in classes)
213         allowedClasses.add(allowedClass);
214
215     classesForSelectorArgument(self, selector, argumentIndex) = WTF::move(allowedClasses);
216 }
217
218 static const char* methodArgumentTypeEncodingForSelector(Protocol *protocol, SEL selector)
219 {
220     // First look at required methods.
221     struct objc_method_description method = protocol_getMethodDescription(protocol, selector, YES, YES);
222     if (method.name)
223         return method.types;
224
225     // Then look at optional methods.
226     method = protocol_getMethodDescription(protocol, selector, NO, YES);
227     if (method.name)
228         return method.types;
229
230     return nullptr;
231 }
232
233 - (NSMethodSignature *)_methodSignatureForSelector:(SEL)selector
234 {
235     const char* types = methodArgumentTypeEncodingForSelector(_protocol, selector);
236     if (!types)
237         return nil;
238
239     return [NSMethodSignature signatureWithObjCTypes:types];
240 }
241
242 - (const Vector<HashSet<Class>>&)_allowedArgumentClassesForSelector:(SEL)selector
243 {
244     ASSERT(_methods.contains(selector));
245
246     return _methods.find(selector)->value.allowedArgumentClasses;
247 }
248
249 @end
250
251 #endif // WK_API_ENABLED