_WKRemoteObjectInterface.mm [plain text]
/*
* Copyright (C) 2013 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "_WKRemoteObjectInterface.h"
#if WK_API_ENABLED
#import <objc/runtime.h>
#import <wtf/HashMap.h>
#import <wtf/Vector.h>
#import <wtf/RetainPtr.h>
extern "C"
const char *_protocol_getMethodTypeEncoding(Protocol *p, SEL sel, BOOL isRequiredMethod, BOOL isInstanceMethod);
@interface NSMethodSignature (WKDetails)
- (Class)_classForObjectAtArgumentIndex:(NSInteger)idx;
@end
@implementation _WKRemoteObjectInterface {
HashMap<SEL, Vector<RetainPtr<NSSet>>> _allowedArgumentClasses;
RetainPtr<NSString> _identifier;
}
static bool isContainerClass(Class objectClass)
{
// FIXME: Add more classes here if needed.
static NSSet *containerClasses = [[NSSet alloc] initWithObjects:[NSArray class], [NSDictionary class], nil];
return [containerClasses containsObject:objectClass];
}
static NSSet *propertyListClasses()
{
// FIXME: Add more property list classes if needed.
static NSSet *propertyListClasses = [[NSSet alloc] initWithObjects:[NSArray class], [NSDictionary class], [NSNumber class], [NSString class], nil];
return propertyListClasses;
}
static Vector<RetainPtr<NSSet>> allowedArgumentClassesForMethod(NSMethodSignature *methodSignature)
{
Vector<RetainPtr<NSSet>> result;
NSSet *emptySet = [NSSet set];
// We ignore self and _cmd.
NSUInteger argumentCount = methodSignature.numberOfArguments - 2;
result.reserveInitialCapacity(argumentCount);
for (NSUInteger i = 0; i < argumentCount; ++i) {
const char* type = [methodSignature getArgumentTypeAtIndex:i + 2];
if (*type != '@') {
// This is a non-object type; we won't allow any classes to be decoded for it.
result.uncheckedAppend(emptySet);
continue;
}
Class objectClass = [methodSignature _classForObjectAtArgumentIndex:i + 2];
if (!objectClass) {
result.uncheckedAppend(emptySet);
continue;
}
if (isContainerClass(objectClass)) {
// For container classes, we allow all simple property list classes.
result.uncheckedAppend(propertyListClasses());
continue;
}
result.uncheckedAppend([NSSet setWithObject:objectClass]);
}
return result;
}
static void initializeAllowedArgumentClasses(_WKRemoteObjectInterface *interface, bool requiredMethods)
{
unsigned methodCount;
struct objc_method_description *methods = protocol_copyMethodDescriptionList(interface->_protocol, requiredMethods, true, &methodCount);
for (unsigned i = 0; i < methodCount; ++i) {
SEL selector = methods[i].name;
const char* types = _protocol_getMethodTypeEncoding(interface->_protocol, selector, requiredMethods, true);
if (!types)
[NSException raise:NSInvalidArgumentException format:@"Could not find method type encoding for method \"%s\"", sel_getName(selector)];
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:types];
interface->_allowedArgumentClasses.set(selector, allowedArgumentClassesForMethod(methodSignature));
}
free(methods);
}
static void initializeAllowedArgumentClasses(_WKRemoteObjectInterface *interface)
{
initializeAllowedArgumentClasses(interface, true);
initializeAllowedArgumentClasses(interface, false);
}
- (id)initWithProtocol:(Protocol *)protocol identifier:(NSString *)identifier
{
if (!(self = [super init]))
return nil;
_protocol = protocol;
_identifier = adoptNS([identifier copy]);
initializeAllowedArgumentClasses(self);
return self;
}
+ (instancetype)remoteObjectInterfaceWithProtocol:(Protocol *)protocol
{
return [[[self alloc] initWithProtocol:protocol identifier:NSStringFromProtocol(protocol)] autorelease];
}
- (NSString *)identifier
{
return _identifier.get();
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; protocol = \"%@\"; identifier = \"%@\">", NSStringFromClass(self.class), self, _identifier.get(), NSStringFromProtocol(_protocol)];
}
static RetainPtr<NSSet>& classesForSelectorArgument(_WKRemoteObjectInterface *interface, SEL selector, NSUInteger argumentIndex)
{
auto it = interface->_allowedArgumentClasses.find(selector);
if (it == interface->_allowedArgumentClasses.end())
[NSException raise:NSInvalidArgumentException format:@"Interface does not contain selector \"%s\"", sel_getName(selector)];
if (argumentIndex >= it->value.size())
[NSException raise:NSInvalidArgumentException format:@"Argument index %ld is out of range for selector \"%s\"", (unsigned long)argumentIndex, sel_getName(selector)];
return it->value[argumentIndex];
}
- (NSSet *)classesForSelector:(SEL)selector argumentIndex:(NSUInteger)argumentIndex
{
return [[classesForSelectorArgument(self, selector, argumentIndex).get() retain] autorelease];
}
- (void)setClasses:(NSSet *)classes forSelector:(SEL)selector argumentIndex:(NSUInteger)argumentIndex
{
classesForSelectorArgument(self, selector, argumentIndex) = adoptNS([classes copy]);
}
static const char* methodArgumentTypeEncodingForSelector(Protocol *protocol, SEL selector)
{
// First look at required methods.
struct objc_method_description method = protocol_getMethodDescription(protocol, selector, YES, YES);
if (method.name)
return method.types;
// Then look at optional methods.
method = protocol_getMethodDescription(protocol, selector, NO, YES);
if (method.name)
return method.types;
return nullptr;
}
- (NSMethodSignature *)_methodSignatureForSelector:(SEL)selector
{
const char* types = methodArgumentTypeEncodingForSelector(_protocol, selector);
if (!types)
return nil;
return [NSMethodSignature signatureWithObjCTypes:types];
}
- (const Vector<RetainPtr<NSSet>>&)_allowedArgumentClassesForSelector:(SEL)selector
{
auto it = _allowedArgumentClasses.find(selector);
ASSERT(it != _allowedArgumentClasses.end());
return it->value;
}
@end
#endif // WK_API_ENABLED