mirror of
https://github.com/darlinghq/darling-JavaScriptCore.git
synced 2024-11-26 21:50:53 +00:00
1151b227f3
I'm honestly surprised that this issue doesn't appear with the older version of JavaScriptCore...
1265 lines
42 KiB
Plaintext
1265 lines
42 KiB
Plaintext
/*
|
|
* Copyright (C) 2013-2018 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. ``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
|
|
* 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 "APICast.h"
|
|
#import "DateInstance.h"
|
|
#import "Error.h"
|
|
#import "Exception.h"
|
|
#import "JavaScriptCore.h"
|
|
#import "JSContextInternal.h"
|
|
#import "JSObjectRefPrivate.h"
|
|
#import "JSVirtualMachineInternal.h"
|
|
#import "JSValueInternal.h"
|
|
#import "JSValuePrivate.h"
|
|
#import "JSWrapperMap.h"
|
|
#import "MarkedJSValueRefArray.h"
|
|
#import "ObjcRuntimeExtras.h"
|
|
#import "JSCInlines.h"
|
|
#import "JSCJSValue.h"
|
|
#import "Strong.h"
|
|
#import "StrongInlines.h"
|
|
#import <wtf/Expected.h>
|
|
#import <wtf/HashMap.h>
|
|
#import <wtf/HashSet.h>
|
|
#import <wtf/Lock.h>
|
|
#import <wtf/Vector.h>
|
|
#import <wtf/text/WTFString.h>
|
|
#import <wtf/text/StringHash.h>
|
|
|
|
#if ENABLE(REMOTE_INSPECTOR)
|
|
#import "CallFrame.h"
|
|
#import "JSGlobalObject.h"
|
|
#import "JSGlobalObjectInspectorController.h"
|
|
#endif
|
|
|
|
#if JSC_OBJC_API_ENABLED
|
|
|
|
NSString * const JSPropertyDescriptorWritableKey = @"writable";
|
|
NSString * const JSPropertyDescriptorEnumerableKey = @"enumerable";
|
|
NSString * const JSPropertyDescriptorConfigurableKey = @"configurable";
|
|
NSString * const JSPropertyDescriptorValueKey = @"value";
|
|
NSString * const JSPropertyDescriptorGetKey = @"get";
|
|
NSString * const JSPropertyDescriptorSetKey = @"set";
|
|
|
|
#if defined(DARLING) && __i386__
|
|
@implementation JSValue
|
|
#else
|
|
@implementation JSValue {
|
|
JSValueRef m_value;
|
|
}
|
|
#endif
|
|
|
|
- (void)dealloc
|
|
{
|
|
JSValueUnprotect([_context JSGlobalContextRef], m_value);
|
|
[_context release];
|
|
_context = nil;
|
|
[super dealloc];
|
|
}
|
|
|
|
- (NSString *)description
|
|
{
|
|
if (id wrapped = tryUnwrapObjcObject([_context JSGlobalContextRef], m_value))
|
|
return [wrapped description];
|
|
return [self toString];
|
|
}
|
|
|
|
- (JSValueRef)JSValueRef
|
|
{
|
|
return m_value;
|
|
}
|
|
|
|
+ (JSValue *)valueWithObject:(id)value inContext:(JSContext *)context
|
|
{
|
|
return [JSValue valueWithJSValueRef:objectToValue(context, value) inContext:context];
|
|
}
|
|
|
|
+ (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context
|
|
{
|
|
return [JSValue valueWithJSValueRef:JSValueMakeBoolean([context JSGlobalContextRef], value) inContext:context];
|
|
}
|
|
|
|
+ (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context
|
|
{
|
|
return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context];
|
|
}
|
|
|
|
+ (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context
|
|
{
|
|
return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context];
|
|
}
|
|
|
|
+ (JSValue *)valueWithUInt32:(uint32_t)value inContext:(JSContext *)context
|
|
{
|
|
return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context];
|
|
}
|
|
|
|
+ (JSValue *)valueWithNewObjectInContext:(JSContext *)context
|
|
{
|
|
return [JSValue valueWithJSValueRef:JSObjectMake([context JSGlobalContextRef], 0, 0) inContext:context];
|
|
}
|
|
|
|
+ (JSValue *)valueWithNewArrayInContext:(JSContext *)context
|
|
{
|
|
return [JSValue valueWithJSValueRef:JSObjectMakeArray([context JSGlobalContextRef], 0, NULL, 0) inContext:context];
|
|
}
|
|
|
|
+ (JSValue *)valueWithNewRegularExpressionFromPattern:(NSString *)pattern flags:(NSString *)flags inContext:(JSContext *)context
|
|
{
|
|
auto patternString = OpaqueJSString::tryCreate(pattern);
|
|
auto flagsString = OpaqueJSString::tryCreate(flags);
|
|
JSValueRef arguments[2] = { JSValueMakeString([context JSGlobalContextRef], patternString.get()), JSValueMakeString([context JSGlobalContextRef], flagsString.get()) };
|
|
return [JSValue valueWithJSValueRef:JSObjectMakeRegExp([context JSGlobalContextRef], 2, arguments, 0) inContext:context];
|
|
}
|
|
|
|
+ (JSValue *)valueWithNewErrorFromMessage:(NSString *)message inContext:(JSContext *)context
|
|
{
|
|
auto string = OpaqueJSString::tryCreate(message);
|
|
JSValueRef argument = JSValueMakeString([context JSGlobalContextRef], string.get());
|
|
return [JSValue valueWithJSValueRef:JSObjectMakeError([context JSGlobalContextRef], 1, &argument, 0) inContext:context];
|
|
}
|
|
|
|
+ (JSValue *)valueWithNullInContext:(JSContext *)context
|
|
{
|
|
return [JSValue valueWithJSValueRef:JSValueMakeNull([context JSGlobalContextRef]) inContext:context];
|
|
}
|
|
|
|
+ (JSValue *)valueWithUndefinedInContext:(JSContext *)context
|
|
{
|
|
return [JSValue valueWithJSValueRef:JSValueMakeUndefined([context JSGlobalContextRef]) inContext:context];
|
|
}
|
|
|
|
+ (JSValue *)valueWithNewSymbolFromDescription:(NSString *)description inContext:(JSContext *)context
|
|
{
|
|
auto string = OpaqueJSString::tryCreate(description);
|
|
return [JSValue valueWithJSValueRef:JSValueMakeSymbol([context JSGlobalContextRef], string.get()) inContext:context];
|
|
}
|
|
|
|
+ (JSValue *)valueWithNewPromiseInContext:(JSContext *)context fromExecutor:(void (^)(JSValue *, JSValue *))executor
|
|
{
|
|
JSObjectRef resolve;
|
|
JSObjectRef reject;
|
|
JSValueRef exception = nullptr;
|
|
JSObjectRef promise = JSObjectMakeDeferredPromise([context JSGlobalContextRef], &resolve, &reject, &exception);
|
|
if (exception) {
|
|
[context notifyException:exception];
|
|
return [JSValue valueWithUndefinedInContext:context];
|
|
}
|
|
|
|
JSValue *result = [JSValue valueWithJSValueRef:promise inContext:context];
|
|
JSValue *rejection = [JSValue valueWithJSValueRef:reject inContext:context];
|
|
CallbackData callbackData;
|
|
const size_t argumentCount = 2;
|
|
JSValueRef arguments[argumentCount];
|
|
arguments[0] = resolve;
|
|
arguments[1] = reject;
|
|
|
|
[context beginCallbackWithData:&callbackData calleeValue:nullptr thisValue:promise argumentCount:argumentCount arguments:arguments];
|
|
executor([JSValue valueWithJSValueRef:resolve inContext:context], rejection);
|
|
if (context.exception)
|
|
[rejection callWithArguments:@[context.exception]];
|
|
[context endCallbackWithData:&callbackData];
|
|
|
|
return result;
|
|
}
|
|
|
|
+ (JSValue *)valueWithNewPromiseResolvedWithResult:(id)result inContext:(JSContext *)context
|
|
{
|
|
return [JSValue valueWithNewPromiseInContext:context fromExecutor:^(JSValue *resolve, JSValue *) {
|
|
[resolve callWithArguments:@[result]];
|
|
}];
|
|
}
|
|
|
|
+ (JSValue *)valueWithNewPromiseRejectedWithReason:(id)reason inContext:(JSContext *)context
|
|
{
|
|
return [JSValue valueWithNewPromiseInContext:context fromExecutor:^(JSValue *, JSValue *reject) {
|
|
[reject callWithArguments:@[reason]];
|
|
}];
|
|
}
|
|
|
|
- (id)toObject
|
|
{
|
|
return valueToObject(_context, m_value);
|
|
}
|
|
|
|
- (id)toObjectOfClass:(Class)expectedClass
|
|
{
|
|
id result = [self toObject];
|
|
return [result isKindOfClass:expectedClass] ? result : nil;
|
|
}
|
|
|
|
- (BOOL)toBool
|
|
{
|
|
return JSValueToBoolean([_context JSGlobalContextRef], m_value);
|
|
}
|
|
|
|
- (double)toDouble
|
|
{
|
|
JSValueRef exception = 0;
|
|
double result = JSValueToNumber([_context JSGlobalContextRef], m_value, &exception);
|
|
if (exception) {
|
|
[_context notifyException:exception];
|
|
return std::numeric_limits<double>::quiet_NaN();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
- (int32_t)toInt32
|
|
{
|
|
return JSC::toInt32([self toDouble]);
|
|
}
|
|
|
|
- (uint32_t)toUInt32
|
|
{
|
|
return JSC::toUInt32([self toDouble]);
|
|
}
|
|
|
|
- (NSNumber *)toNumber
|
|
{
|
|
JSValueRef exception = 0;
|
|
id result = valueToNumber([_context JSGlobalContextRef], m_value, &exception);
|
|
if (exception)
|
|
[_context notifyException:exception];
|
|
return result;
|
|
}
|
|
|
|
- (NSString *)toString
|
|
{
|
|
JSValueRef exception = 0;
|
|
id result = valueToString([_context JSGlobalContextRef], m_value, &exception);
|
|
if (exception)
|
|
[_context notifyException:exception];
|
|
return result;
|
|
}
|
|
|
|
- (NSDate *)toDate
|
|
{
|
|
JSValueRef exception = 0;
|
|
id result = valueToDate([_context JSGlobalContextRef], m_value, &exception);
|
|
if (exception)
|
|
[_context notifyException:exception];
|
|
return result;
|
|
}
|
|
|
|
- (NSArray *)toArray
|
|
{
|
|
JSValueRef exception = 0;
|
|
id result = valueToArray([_context JSGlobalContextRef], m_value, &exception);
|
|
if (exception)
|
|
[_context notifyException:exception];
|
|
return result;
|
|
}
|
|
|
|
- (NSDictionary *)toDictionary
|
|
{
|
|
JSValueRef exception = 0;
|
|
id result = valueToDictionary([_context JSGlobalContextRef], m_value, &exception);
|
|
if (exception)
|
|
[_context notifyException:exception];
|
|
return result;
|
|
}
|
|
|
|
template<typename Result, typename NSStringFunction, typename JSValueFunction, typename... Types>
|
|
inline Expected<Result, JSValueRef> performPropertyOperation(NSStringFunction stringFunction, JSValueFunction jsFunction, JSValue* value, id propertyKey, Types... arguments)
|
|
{
|
|
JSContext* context = [value context];
|
|
JSValueRef exception = nullptr;
|
|
JSObjectRef object = JSValueToObject([context JSGlobalContextRef], [value JSValueRef], &exception);
|
|
if (exception)
|
|
return Unexpected<JSValueRef>(exception);
|
|
|
|
Result result;
|
|
// If it's a NSString already, reduce indirection and just pass the NSString.
|
|
if ([propertyKey isKindOfClass:[NSString class]]) {
|
|
auto name = OpaqueJSString::tryCreate((NSString *)propertyKey);
|
|
result = stringFunction([context JSGlobalContextRef], object, name.get(), arguments..., &exception);
|
|
} else
|
|
result = jsFunction([context JSGlobalContextRef], object, [[JSValue valueWithObject:propertyKey inContext:context] JSValueRef], arguments..., &exception);
|
|
return Expected<Result, JSValueRef>(result);
|
|
}
|
|
|
|
- (JSValue *)valueForProperty:(id)key
|
|
{
|
|
auto result = performPropertyOperation<JSValueRef>(JSObjectGetProperty, JSObjectGetPropertyForKey, self, key);
|
|
if (!result)
|
|
return [_context valueFromNotifyException:result.error()];
|
|
|
|
return [JSValue valueWithJSValueRef:result.value() inContext:_context];
|
|
}
|
|
|
|
|
|
- (void)setValue:(id)value forProperty:(JSValueProperty)key
|
|
{
|
|
// We need Unit business because void can't be assigned to in performPropertyOperation and I don't want to duplicate the code...
|
|
using Unit = std::tuple<>;
|
|
auto stringSetProperty = [] (auto... args) -> Unit {
|
|
JSObjectSetProperty(args...);
|
|
return { };
|
|
};
|
|
|
|
auto jsValueSetProperty = [] (auto... args) -> Unit {
|
|
JSObjectSetPropertyForKey(args...);
|
|
return { };
|
|
};
|
|
|
|
auto result = performPropertyOperation<Unit>(stringSetProperty, jsValueSetProperty, self, key, objectToValue(_context, value), kJSPropertyAttributeNone);
|
|
if (!result) {
|
|
[_context notifyException:result.error()];
|
|
return;
|
|
}
|
|
}
|
|
|
|
- (BOOL)deleteProperty:(JSValueProperty)key
|
|
{
|
|
Expected<BOOL, JSValueRef> result = performPropertyOperation<BOOL>(JSObjectDeleteProperty, JSObjectDeletePropertyForKey, self, key);
|
|
if (!result)
|
|
return [_context boolFromNotifyException:result.error()];
|
|
return result.value();
|
|
}
|
|
|
|
- (BOOL)hasProperty:(JSValueProperty)key
|
|
{
|
|
// The C-api doesn't return an exception value for the string version of has property.
|
|
auto stringHasProperty = [] (JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef*) -> BOOL {
|
|
return JSObjectHasProperty(ctx, object, propertyName);
|
|
};
|
|
|
|
Expected<BOOL, JSValueRef> result = performPropertyOperation<BOOL>(stringHasProperty, JSObjectHasPropertyForKey, self, key);
|
|
if (!result)
|
|
return [_context boolFromNotifyException:result.error()];
|
|
return result.value();
|
|
}
|
|
|
|
- (void)defineProperty:(JSValueProperty)key descriptor:(id)descriptor
|
|
{
|
|
[[_context globalObject][@"Object"] invokeMethod:@"defineProperty" withArguments:@[ self, key, descriptor ]];
|
|
}
|
|
|
|
- (JSValue *)valueAtIndex:(NSUInteger)index
|
|
{
|
|
// Properties that are higher than an unsigned value can hold are converted to a double then inserted as a normal property.
|
|
// Indices that are bigger than the max allowed index size (UINT_MAX - 1) will be handled internally in get().
|
|
if (index != (unsigned)index)
|
|
return [self valueForProperty:[[JSValue valueWithDouble:index inContext:_context] toString]];
|
|
|
|
JSValueRef exception = 0;
|
|
JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
|
|
if (exception)
|
|
return [_context valueFromNotifyException:exception];
|
|
|
|
JSValueRef result = JSObjectGetPropertyAtIndex([_context JSGlobalContextRef], object, (unsigned)index, &exception);
|
|
if (exception)
|
|
return [_context valueFromNotifyException:exception];
|
|
|
|
return [JSValue valueWithJSValueRef:result inContext:_context];
|
|
}
|
|
|
|
- (void)setValue:(id)value atIndex:(NSUInteger)index
|
|
{
|
|
// Properties that are higher than an unsigned value can hold are converted to a double, then inserted as a normal property.
|
|
// Indices that are bigger than the max allowed index size (UINT_MAX - 1) will be handled internally in putByIndex().
|
|
if (index != (unsigned)index)
|
|
return [self setValue:value forProperty:[[JSValue valueWithDouble:index inContext:_context] toString]];
|
|
|
|
JSValueRef exception = 0;
|
|
JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
|
|
if (exception) {
|
|
[_context notifyException:exception];
|
|
return;
|
|
}
|
|
|
|
JSObjectSetPropertyAtIndex([_context JSGlobalContextRef], object, (unsigned)index, objectToValue(_context, value), &exception);
|
|
if (exception) {
|
|
[_context notifyException:exception];
|
|
return;
|
|
}
|
|
}
|
|
|
|
- (BOOL)isUndefined
|
|
{
|
|
#if !CPU(ADDRESS64)
|
|
return JSValueIsUndefined([_context JSGlobalContextRef], m_value);
|
|
#else
|
|
return toJS(m_value).isUndefined();
|
|
#endif
|
|
}
|
|
|
|
- (BOOL)isNull
|
|
{
|
|
#if !CPU(ADDRESS64)
|
|
return JSValueIsNull([_context JSGlobalContextRef], m_value);
|
|
#else
|
|
return toJS(m_value).isNull();
|
|
#endif
|
|
}
|
|
|
|
- (BOOL)isBoolean
|
|
{
|
|
#if !CPU(ADDRESS64)
|
|
return JSValueIsBoolean([_context JSGlobalContextRef], m_value);
|
|
#else
|
|
return toJS(m_value).isBoolean();
|
|
#endif
|
|
}
|
|
|
|
- (BOOL)isNumber
|
|
{
|
|
#if !CPU(ADDRESS64)
|
|
return JSValueIsNumber([_context JSGlobalContextRef], m_value);
|
|
#else
|
|
return toJS(m_value).isNumber();
|
|
#endif
|
|
}
|
|
|
|
- (BOOL)isString
|
|
{
|
|
#if !CPU(ADDRESS64)
|
|
return JSValueIsString([_context JSGlobalContextRef], m_value);
|
|
#else
|
|
return toJS(m_value).isString();
|
|
#endif
|
|
}
|
|
|
|
- (BOOL)isObject
|
|
{
|
|
#if !CPU(ADDRESS64)
|
|
return JSValueIsObject([_context JSGlobalContextRef], m_value);
|
|
#else
|
|
return toJS(m_value).isObject();
|
|
#endif
|
|
}
|
|
|
|
- (BOOL)isSymbol
|
|
{
|
|
#if !CPU(ADDRESS64)
|
|
return JSValueIsSymbol([_context JSGlobalContextRef], m_value);
|
|
#else
|
|
return toJS(m_value).isSymbol();
|
|
#endif
|
|
}
|
|
|
|
- (BOOL)isArray
|
|
{
|
|
return JSValueIsArray([_context JSGlobalContextRef], m_value);
|
|
}
|
|
|
|
- (BOOL)isDate
|
|
{
|
|
return JSValueIsDate([_context JSGlobalContextRef], m_value);
|
|
}
|
|
|
|
- (BOOL)isEqualToObject:(id)value
|
|
{
|
|
return JSValueIsStrictEqual([_context JSGlobalContextRef], m_value, objectToValue(_context, value));
|
|
}
|
|
|
|
- (BOOL)isEqualWithTypeCoercionToObject:(id)value
|
|
{
|
|
JSValueRef exception = 0;
|
|
BOOL result = JSValueIsEqual([_context JSGlobalContextRef], m_value, objectToValue(_context, value), &exception);
|
|
if (exception)
|
|
return [_context boolFromNotifyException:exception];
|
|
|
|
return result;
|
|
}
|
|
|
|
- (BOOL)isInstanceOf:(id)value
|
|
{
|
|
JSValueRef exception = 0;
|
|
JSObjectRef constructor = JSValueToObject([_context JSGlobalContextRef], objectToValue(_context, value), &exception);
|
|
if (exception)
|
|
return [_context boolFromNotifyException:exception];
|
|
|
|
BOOL result = JSValueIsInstanceOfConstructor([_context JSGlobalContextRef], m_value, constructor, &exception);
|
|
if (exception)
|
|
return [_context boolFromNotifyException:exception];
|
|
|
|
return result;
|
|
}
|
|
|
|
- (JSValue *)callWithArguments:(NSArray *)argumentArray
|
|
{
|
|
JSC::JSGlobalObject* globalObject = toJS([_context JSGlobalContextRef]);
|
|
JSC::VM& vm = globalObject->vm();
|
|
JSC::JSLockHolder locker(vm);
|
|
|
|
NSUInteger argumentCount = [argumentArray count];
|
|
JSC::MarkedJSValueRefArray arguments([_context JSGlobalContextRef], argumentCount);
|
|
for (unsigned i = 0; i < argumentCount; ++i)
|
|
arguments[i] = objectToValue(_context, [argumentArray objectAtIndex:i]);
|
|
|
|
JSValueRef exception = 0;
|
|
JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
|
|
if (exception)
|
|
return [_context valueFromNotifyException:exception];
|
|
|
|
JSValueRef result = JSObjectCallAsFunction([_context JSGlobalContextRef], object, 0, argumentCount, arguments.data(), &exception);
|
|
if (exception)
|
|
return [_context valueFromNotifyException:exception];
|
|
|
|
return [JSValue valueWithJSValueRef:result inContext:_context];
|
|
}
|
|
|
|
- (JSValue *)constructWithArguments:(NSArray *)argumentArray
|
|
{
|
|
JSC::JSGlobalObject* globalObject = toJS([_context JSGlobalContextRef]);
|
|
JSC::VM& vm = globalObject->vm();
|
|
JSC::JSLockHolder locker(vm);
|
|
|
|
NSUInteger argumentCount = [argumentArray count];
|
|
JSC::MarkedJSValueRefArray arguments([_context JSGlobalContextRef], argumentCount);
|
|
for (unsigned i = 0; i < argumentCount; ++i)
|
|
arguments[i] = objectToValue(_context, [argumentArray objectAtIndex:i]);
|
|
|
|
JSValueRef exception = 0;
|
|
JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
|
|
if (exception)
|
|
return [_context valueFromNotifyException:exception];
|
|
|
|
JSObjectRef result = JSObjectCallAsConstructor([_context JSGlobalContextRef], object, argumentCount, arguments.data(), &exception);
|
|
if (exception)
|
|
return [_context valueFromNotifyException:exception];
|
|
|
|
return [JSValue valueWithJSValueRef:result inContext:_context];
|
|
}
|
|
|
|
- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments
|
|
{
|
|
JSC::JSGlobalObject* globalObject = toJS([_context JSGlobalContextRef]);
|
|
JSC::VM& vm = globalObject->vm();
|
|
JSC::JSLockHolder locker(vm);
|
|
|
|
NSUInteger argumentCount = [arguments count];
|
|
JSC::MarkedJSValueRefArray argumentArray([_context JSGlobalContextRef], argumentCount);
|
|
for (unsigned i = 0; i < argumentCount; ++i)
|
|
argumentArray[i] = objectToValue(_context, [arguments objectAtIndex:i]);
|
|
|
|
JSValueRef exception = 0;
|
|
JSObjectRef thisObject = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
|
|
if (exception)
|
|
return [_context valueFromNotifyException:exception];
|
|
|
|
auto name = OpaqueJSString::tryCreate(method);
|
|
JSValueRef function = JSObjectGetProperty([_context JSGlobalContextRef], thisObject, name.get(), &exception);
|
|
if (exception)
|
|
return [_context valueFromNotifyException:exception];
|
|
|
|
JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], function, &exception);
|
|
if (exception)
|
|
return [_context valueFromNotifyException:exception];
|
|
|
|
JSValueRef result = JSObjectCallAsFunction([_context JSGlobalContextRef], object, thisObject, argumentCount, argumentArray.data(), &exception);
|
|
if (exception)
|
|
return [_context valueFromNotifyException:exception];
|
|
|
|
return [JSValue valueWithJSValueRef:result inContext:_context];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation JSValue(StructSupport)
|
|
|
|
- (CGPoint)toPoint
|
|
{
|
|
return (CGPoint){
|
|
static_cast<CGFloat>([self[@"x"] toDouble]),
|
|
static_cast<CGFloat>([self[@"y"] toDouble])
|
|
};
|
|
}
|
|
|
|
- (NSRange)toRange
|
|
{
|
|
return (NSRange){
|
|
[[self[@"location"] toNumber] unsignedIntegerValue],
|
|
[[self[@"length"] toNumber] unsignedIntegerValue]
|
|
};
|
|
}
|
|
|
|
- (CGRect)toRect
|
|
{
|
|
return (CGRect){
|
|
[self toPoint],
|
|
[self toSize]
|
|
};
|
|
}
|
|
|
|
- (CGSize)toSize
|
|
{
|
|
return (CGSize){
|
|
static_cast<CGFloat>([self[@"width"] toDouble]),
|
|
static_cast<CGFloat>([self[@"height"] toDouble])
|
|
};
|
|
}
|
|
|
|
+ (JSValue *)valueWithPoint:(CGPoint)point inContext:(JSContext *)context
|
|
{
|
|
return [JSValue valueWithObject:@{
|
|
@"x":@(point.x),
|
|
@"y":@(point.y)
|
|
} inContext:context];
|
|
}
|
|
|
|
+ (JSValue *)valueWithRange:(NSRange)range inContext:(JSContext *)context
|
|
{
|
|
return [JSValue valueWithObject:@{
|
|
@"location":@(range.location),
|
|
@"length":@(range.length)
|
|
} inContext:context];
|
|
}
|
|
|
|
+ (JSValue *)valueWithRect:(CGRect)rect inContext:(JSContext *)context
|
|
{
|
|
return [JSValue valueWithObject:@{
|
|
@"x":@(rect.origin.x),
|
|
@"y":@(rect.origin.y),
|
|
@"width":@(rect.size.width),
|
|
@"height":@(rect.size.height)
|
|
} inContext:context];
|
|
}
|
|
|
|
+ (JSValue *)valueWithSize:(CGSize)size inContext:(JSContext *)context
|
|
{
|
|
return [JSValue valueWithObject:@{
|
|
@"width":@(size.width),
|
|
@"height":@(size.height)
|
|
} inContext:context];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation JSValue(SubscriptSupport)
|
|
|
|
- (JSValue *)objectForKeyedSubscript:(id)key
|
|
{
|
|
return [self valueForProperty:key];
|
|
}
|
|
|
|
- (JSValue *)objectAtIndexedSubscript:(NSUInteger)index
|
|
{
|
|
return [self valueAtIndex:index];
|
|
}
|
|
|
|
- (void)setObject:(id)object forKeyedSubscript:(id)key
|
|
{
|
|
[self setValue:object forProperty:key];
|
|
}
|
|
|
|
- (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index
|
|
{
|
|
[self setValue:object atIndex:index];
|
|
}
|
|
|
|
@end
|
|
|
|
inline bool isDate(JSC::VM& vm, JSObjectRef object, JSGlobalContextRef context)
|
|
{
|
|
JSC::JSLockHolder locker(toJS(context));
|
|
return toJS(object)->inherits<JSC::DateInstance>(vm);
|
|
}
|
|
|
|
inline bool isArray(JSC::VM& vm, JSObjectRef object, JSGlobalContextRef context)
|
|
{
|
|
JSC::JSLockHolder locker(toJS(context));
|
|
return toJS(object)->inherits<JSC::JSArray>(vm);
|
|
}
|
|
|
|
@implementation JSValue(Internal)
|
|
|
|
enum ConversionType {
|
|
ContainerNone,
|
|
ContainerArray,
|
|
ContainerDictionary
|
|
};
|
|
|
|
class JSContainerConvertor {
|
|
public:
|
|
struct Task {
|
|
JSValueRef js;
|
|
id objc;
|
|
ConversionType type;
|
|
};
|
|
|
|
JSContainerConvertor(JSGlobalContextRef context)
|
|
: m_context(context)
|
|
{
|
|
}
|
|
|
|
id convert(JSValueRef property);
|
|
void add(Task);
|
|
Task take();
|
|
bool isWorkListEmpty() const { return !m_worklist.size(); }
|
|
|
|
private:
|
|
JSGlobalContextRef m_context;
|
|
HashMap<JSValueRef, __unsafe_unretained id> m_objectMap;
|
|
Vector<Task> m_worklist;
|
|
Vector<JSC::Strong<JSC::Unknown>> m_jsValues;
|
|
};
|
|
|
|
inline id JSContainerConvertor::convert(JSValueRef value)
|
|
{
|
|
auto iter = m_objectMap.find(value);
|
|
if (iter != m_objectMap.end())
|
|
return iter->value;
|
|
|
|
Task result = valueToObjectWithoutCopy(m_context, value);
|
|
if (result.js)
|
|
add(result);
|
|
return result.objc;
|
|
}
|
|
|
|
void JSContainerConvertor::add(Task task)
|
|
{
|
|
JSC::JSGlobalObject* globalObject = toJS(m_context);
|
|
m_jsValues.append(JSC::Strong<JSC::Unknown>(globalObject->vm(), toJSForGC(globalObject, task.js)));
|
|
m_objectMap.add(task.js, task.objc);
|
|
if (task.type != ContainerNone)
|
|
m_worklist.append(task);
|
|
}
|
|
|
|
JSContainerConvertor::Task JSContainerConvertor::take()
|
|
{
|
|
ASSERT(!isWorkListEmpty());
|
|
Task last = m_worklist.last();
|
|
m_worklist.removeLast();
|
|
return last;
|
|
}
|
|
|
|
#if ENABLE(REMOTE_INSPECTOR)
|
|
static void reportExceptionToInspector(JSGlobalContextRef context, JSC::JSValue exceptionValue)
|
|
{
|
|
JSC::JSGlobalObject* globalObject = toJS(context);
|
|
JSC::VM& vm = globalObject->vm();
|
|
JSC::Exception* exception = JSC::Exception::create(vm, exceptionValue);
|
|
globalObject->inspectorController().reportAPIException(globalObject, exception);
|
|
}
|
|
#endif
|
|
|
|
static JSContainerConvertor::Task valueToObjectWithoutCopy(JSGlobalContextRef context, JSValueRef value)
|
|
{
|
|
JSC::JSGlobalObject* globalObject = toJS(context);
|
|
JSC::VM& vm = globalObject->vm();
|
|
|
|
if (!JSValueIsObject(context, value)) {
|
|
id primitive;
|
|
if (JSValueIsBoolean(context, value))
|
|
primitive = JSValueToBoolean(context, value) ? @YES : @NO;
|
|
else if (JSValueIsNumber(context, value)) {
|
|
// Normalize the number, so it will unique correctly in the hash map -
|
|
// it's nicer not to leak this internal implementation detail!
|
|
value = JSValueMakeNumber(context, JSValueToNumber(context, value, 0));
|
|
primitive = @(JSValueToNumber(context, value, 0));
|
|
} else if (JSValueIsString(context, value)) {
|
|
// Would be nice to unique strings, too.
|
|
auto jsstring = adoptRef(JSValueToStringCopy(context, value, 0));
|
|
primitive = CFBridgingRelease(JSStringCopyCFString(kCFAllocatorDefault, jsstring.get()));
|
|
} else if (JSValueIsNull(context, value))
|
|
primitive = [NSNull null];
|
|
else {
|
|
ASSERT(JSValueIsUndefined(context, value));
|
|
primitive = nil;
|
|
}
|
|
return { value, primitive, ContainerNone };
|
|
}
|
|
|
|
JSObjectRef object = JSValueToObject(context, value, 0);
|
|
|
|
if (id wrapped = tryUnwrapObjcObject(context, object))
|
|
return { object, wrapped, ContainerNone };
|
|
|
|
if (isDate(vm, object, context))
|
|
return { object, [NSDate dateWithTimeIntervalSince1970:JSValueToNumber(context, object, 0) / 1000.0], ContainerNone };
|
|
|
|
if (isArray(vm, object, context))
|
|
return { object, [NSMutableArray array], ContainerArray };
|
|
|
|
return { object, [NSMutableDictionary dictionary], ContainerDictionary };
|
|
}
|
|
|
|
static id containerValueToObject(JSGlobalContextRef context, JSContainerConvertor::Task task)
|
|
{
|
|
ASSERT(task.type != ContainerNone);
|
|
JSC::JSLockHolder locker(toJS(context));
|
|
JSContainerConvertor convertor(context);
|
|
convertor.add(task);
|
|
ASSERT(!convertor.isWorkListEmpty());
|
|
|
|
do {
|
|
JSContainerConvertor::Task current = convertor.take();
|
|
ASSERT(JSValueIsObject(context, current.js));
|
|
JSObjectRef js = JSValueToObject(context, current.js, 0);
|
|
|
|
if (current.type == ContainerArray) {
|
|
ASSERT([current.objc isKindOfClass:[NSMutableArray class]]);
|
|
NSMutableArray *array = (NSMutableArray *)current.objc;
|
|
|
|
auto lengthString = OpaqueJSString::tryCreate("length"_s);
|
|
unsigned length = JSC::toUInt32(JSValueToNumber(context, JSObjectGetProperty(context, js, lengthString.get(), 0), 0));
|
|
|
|
for (unsigned i = 0; i < length; ++i) {
|
|
id objc = convertor.convert(JSObjectGetPropertyAtIndex(context, js, i, 0));
|
|
[array addObject:objc ? objc : [NSNull null]];
|
|
}
|
|
} else {
|
|
ASSERT([current.objc isKindOfClass:[NSMutableDictionary class]]);
|
|
NSMutableDictionary *dictionary = (NSMutableDictionary *)current.objc;
|
|
|
|
JSC::JSLockHolder locker(toJS(context));
|
|
|
|
JSPropertyNameArrayRef propertyNameArray = JSObjectCopyPropertyNames(context, js);
|
|
size_t length = JSPropertyNameArrayGetCount(propertyNameArray);
|
|
|
|
for (size_t i = 0; i < length; ++i) {
|
|
JSStringRef propertyName = JSPropertyNameArrayGetNameAtIndex(propertyNameArray, i);
|
|
if (id objc = convertor.convert(JSObjectGetProperty(context, js, propertyName, 0)))
|
|
dictionary[(__bridge NSString *)adoptCF(JSStringCopyCFString(kCFAllocatorDefault, propertyName)).get()] = objc;
|
|
}
|
|
|
|
JSPropertyNameArrayRelease(propertyNameArray);
|
|
}
|
|
|
|
} while (!convertor.isWorkListEmpty());
|
|
|
|
return task.objc;
|
|
}
|
|
|
|
id valueToObject(JSContext *context, JSValueRef value)
|
|
{
|
|
JSContainerConvertor::Task result = valueToObjectWithoutCopy([context JSGlobalContextRef], value);
|
|
if (result.type == ContainerNone)
|
|
return result.objc;
|
|
return containerValueToObject([context JSGlobalContextRef], result);
|
|
}
|
|
|
|
id valueToNumber(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
|
|
{
|
|
ASSERT(!*exception);
|
|
if (id wrapped = tryUnwrapObjcObject(context, value)) {
|
|
if ([wrapped isKindOfClass:[NSNumber class]])
|
|
return wrapped;
|
|
}
|
|
|
|
if (JSValueIsBoolean(context, value))
|
|
return JSValueToBoolean(context, value) ? @YES : @NO;
|
|
|
|
double result = JSValueToNumber(context, value, exception);
|
|
return @(*exception ? std::numeric_limits<double>::quiet_NaN() : result);
|
|
}
|
|
|
|
id valueToString(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
|
|
{
|
|
ASSERT(!*exception);
|
|
if (id wrapped = tryUnwrapObjcObject(context, value)) {
|
|
if ([wrapped isKindOfClass:[NSString class]])
|
|
return wrapped;
|
|
}
|
|
|
|
auto jsstring = adoptRef(JSValueToStringCopy(context, value, exception));
|
|
if (*exception) {
|
|
ASSERT(!jsstring);
|
|
return nil;
|
|
}
|
|
|
|
return CFBridgingRelease(JSStringCopyCFString(kCFAllocatorDefault, jsstring.get()));
|
|
}
|
|
|
|
id valueToDate(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
|
|
{
|
|
ASSERT(!*exception);
|
|
if (id wrapped = tryUnwrapObjcObject(context, value)) {
|
|
if ([wrapped isKindOfClass:[NSDate class]])
|
|
return wrapped;
|
|
}
|
|
|
|
double result = JSValueToNumber(context, value, exception) / 1000.0;
|
|
return *exception ? nil : [NSDate dateWithTimeIntervalSince1970:result];
|
|
}
|
|
|
|
id valueToArray(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
|
|
{
|
|
ASSERT(!*exception);
|
|
if (id wrapped = tryUnwrapObjcObject(context, value)) {
|
|
if ([wrapped isKindOfClass:[NSArray class]])
|
|
return wrapped;
|
|
}
|
|
|
|
if (JSValueIsObject(context, value))
|
|
return containerValueToObject(context, { value, [NSMutableArray array], ContainerArray});
|
|
|
|
JSC::JSLockHolder locker(toJS(context));
|
|
if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value))) {
|
|
JSC::JSObject* exceptionObject = JSC::createTypeError(toJS(context), "Cannot convert primitive to NSArray"_s);
|
|
*exception = toRef(exceptionObject);
|
|
#if ENABLE(REMOTE_INSPECTOR)
|
|
reportExceptionToInspector(context, exceptionObject);
|
|
#endif
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
id valueToDictionary(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
|
|
{
|
|
ASSERT(!*exception);
|
|
if (id wrapped = tryUnwrapObjcObject(context, value)) {
|
|
if ([wrapped isKindOfClass:[NSDictionary class]])
|
|
return wrapped;
|
|
}
|
|
|
|
if (JSValueIsObject(context, value))
|
|
return containerValueToObject(context, { value, [NSMutableDictionary dictionary], ContainerDictionary});
|
|
|
|
JSC::JSLockHolder locker(toJS(context));
|
|
if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value))) {
|
|
JSC::JSObject* exceptionObject = JSC::createTypeError(toJS(context), "Cannot convert primitive to NSDictionary"_s);
|
|
*exception = toRef(exceptionObject);
|
|
#if ENABLE(REMOTE_INSPECTOR)
|
|
reportExceptionToInspector(context, exceptionObject);
|
|
#endif
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
class ObjcContainerConvertor {
|
|
public:
|
|
struct Task {
|
|
id objc;
|
|
JSValueRef js;
|
|
ConversionType type;
|
|
};
|
|
|
|
ObjcContainerConvertor(JSContext *context)
|
|
: m_context(context)
|
|
{
|
|
}
|
|
|
|
JSValueRef convert(id object);
|
|
void add(Task);
|
|
Task take();
|
|
bool isWorkListEmpty() const { return !m_worklist.size(); }
|
|
|
|
private:
|
|
JSContext *m_context;
|
|
HashMap<__unsafe_unretained id, JSValueRef> m_objectMap;
|
|
Vector<Task> m_worklist;
|
|
Vector<JSC::Strong<JSC::Unknown>> m_jsValues;
|
|
};
|
|
|
|
JSValueRef ObjcContainerConvertor::convert(id object)
|
|
{
|
|
ASSERT(object);
|
|
|
|
auto it = m_objectMap.find(object);
|
|
if (it != m_objectMap.end())
|
|
return it->value;
|
|
|
|
ObjcContainerConvertor::Task task = objectToValueWithoutCopy(m_context, object);
|
|
add(task);
|
|
return task.js;
|
|
}
|
|
|
|
void ObjcContainerConvertor::add(ObjcContainerConvertor::Task task)
|
|
{
|
|
JSC::JSGlobalObject* globalObject = toJS(m_context.JSGlobalContextRef);
|
|
m_jsValues.append(JSC::Strong<JSC::Unknown>(globalObject->vm(), toJSForGC(globalObject, task.js)));
|
|
m_objectMap.add(task.objc, task.js);
|
|
if (task.type != ContainerNone)
|
|
m_worklist.append(task);
|
|
}
|
|
|
|
ObjcContainerConvertor::Task ObjcContainerConvertor::take()
|
|
{
|
|
ASSERT(!isWorkListEmpty());
|
|
Task last = m_worklist.last();
|
|
m_worklist.removeLast();
|
|
return last;
|
|
}
|
|
|
|
inline bool isNSBoolean(id object)
|
|
{
|
|
ASSERT([@YES class] == [@NO class]);
|
|
ASSERT([@YES class] != [NSNumber class]);
|
|
ASSERT([[@YES class] isSubclassOfClass:[NSNumber class]]);
|
|
return [object isKindOfClass:[@YES class]];
|
|
}
|
|
|
|
static ObjcContainerConvertor::Task objectToValueWithoutCopy(JSContext *context, id object)
|
|
{
|
|
JSGlobalContextRef contextRef = [context JSGlobalContextRef];
|
|
|
|
if (!object)
|
|
return { object, JSValueMakeUndefined(contextRef), ContainerNone };
|
|
|
|
if (!class_conformsToProtocol(object_getClass(object), getJSExportProtocol())) {
|
|
if ([object isKindOfClass:[NSArray class]])
|
|
return { object, JSObjectMakeArray(contextRef, 0, NULL, 0), ContainerArray };
|
|
|
|
if ([object isKindOfClass:[NSDictionary class]])
|
|
return { object, JSObjectMake(contextRef, 0, 0), ContainerDictionary };
|
|
|
|
if ([object isKindOfClass:[NSNull class]])
|
|
return { object, JSValueMakeNull(contextRef), ContainerNone };
|
|
|
|
if ([object isKindOfClass:[JSValue class]])
|
|
return { object, ((JSValue *)object)->m_value, ContainerNone };
|
|
|
|
if ([object isKindOfClass:[NSString class]]) {
|
|
auto string = OpaqueJSString::tryCreate((NSString *)object);
|
|
return { object, JSValueMakeString(contextRef, string.get()), ContainerNone };
|
|
}
|
|
|
|
if ([object isKindOfClass:[NSNumber class]]) {
|
|
if (isNSBoolean(object))
|
|
return { object, JSValueMakeBoolean(contextRef, [object boolValue]), ContainerNone };
|
|
return { object, JSValueMakeNumber(contextRef, [object doubleValue]), ContainerNone };
|
|
}
|
|
|
|
if ([object isKindOfClass:[NSDate class]]) {
|
|
JSValueRef argument = JSValueMakeNumber(contextRef, [object timeIntervalSince1970] * 1000.0);
|
|
JSObjectRef result = JSObjectMakeDate(contextRef, 1, &argument, 0);
|
|
return { object, result, ContainerNone };
|
|
}
|
|
|
|
if ([object isKindOfClass:[JSManagedValue class]]) {
|
|
JSValue *value = [static_cast<JSManagedValue *>(object) value];
|
|
if (!value)
|
|
return { object, JSValueMakeUndefined(contextRef), ContainerNone };
|
|
return { object, value->m_value, ContainerNone };
|
|
}
|
|
}
|
|
|
|
return { object, valueInternalValue([context wrapperForObjCObject:object]), ContainerNone };
|
|
}
|
|
|
|
JSValueRef objectToValue(JSContext *context, id object)
|
|
{
|
|
JSGlobalContextRef contextRef = [context JSGlobalContextRef];
|
|
|
|
ObjcContainerConvertor::Task task = objectToValueWithoutCopy(context, object);
|
|
if (task.type == ContainerNone)
|
|
return task.js;
|
|
|
|
JSC::JSLockHolder locker(toJS(contextRef));
|
|
ObjcContainerConvertor convertor(context);
|
|
convertor.add(task);
|
|
ASSERT(!convertor.isWorkListEmpty());
|
|
|
|
do {
|
|
ObjcContainerConvertor::Task current = convertor.take();
|
|
ASSERT(JSValueIsObject(contextRef, current.js));
|
|
JSObjectRef js = JSValueToObject(contextRef, current.js, 0);
|
|
|
|
if (current.type == ContainerArray) {
|
|
ASSERT([current.objc isKindOfClass:[NSArray class]]);
|
|
NSArray *array = (NSArray *)current.objc;
|
|
NSUInteger count = [array count];
|
|
for (NSUInteger index = 0; index < count; ++index)
|
|
JSObjectSetPropertyAtIndex(contextRef, js, index, convertor.convert([array objectAtIndex:index]), 0);
|
|
} else {
|
|
ASSERT(current.type == ContainerDictionary);
|
|
ASSERT([current.objc isKindOfClass:[NSDictionary class]]);
|
|
NSDictionary *dictionary = (NSDictionary *)current.objc;
|
|
for (id key in [dictionary keyEnumerator]) {
|
|
if ([key isKindOfClass:[NSString class]]) {
|
|
auto propertyName = OpaqueJSString::tryCreate((NSString *)key);
|
|
JSObjectSetProperty(contextRef, js, propertyName.get(), convertor.convert([dictionary objectForKey:key]), 0, 0);
|
|
}
|
|
}
|
|
}
|
|
} while (!convertor.isWorkListEmpty());
|
|
|
|
return task.js;
|
|
}
|
|
|
|
JSValueRef valueInternalValue(JSValue * value)
|
|
{
|
|
return value->m_value;
|
|
}
|
|
|
|
+ (JSValue *)valueWithJSValueRef:(JSValueRef)value inContext:(JSContext *)context
|
|
{
|
|
return [context wrapperForJSObject:value];
|
|
}
|
|
|
|
- (JSValue *)init
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
- (JSValue *)initWithValue:(JSValueRef)value inContext:(JSContext *)context
|
|
{
|
|
if (!value || !context)
|
|
return nil;
|
|
|
|
self = [super init];
|
|
if (!self)
|
|
return nil;
|
|
|
|
_context = [context retain];
|
|
m_value = value;
|
|
JSValueProtect([_context JSGlobalContextRef], m_value);
|
|
return self;
|
|
}
|
|
|
|
struct StructTagHandler {
|
|
SEL typeToValueSEL;
|
|
SEL valueToTypeSEL;
|
|
};
|
|
typedef HashMap<String, StructTagHandler> StructHandlers;
|
|
|
|
static StructHandlers* createStructHandlerMap()
|
|
{
|
|
StructHandlers* structHandlers = new StructHandlers();
|
|
|
|
size_t valueWithXinContextLength = strlen("valueWithX:inContext:");
|
|
size_t toXLength = strlen("toX");
|
|
|
|
// Step 1: find all valueWith<Foo>:inContext: class methods in JSValue.
|
|
forEachMethodInClass(object_getClass([JSValue class]), ^(Method method){
|
|
SEL selector = method_getName(method);
|
|
const char* name = sel_getName(selector);
|
|
size_t nameLength = strlen(name);
|
|
// Check for valueWith<Foo>:context:
|
|
if (nameLength < valueWithXinContextLength || memcmp(name, "valueWith", 9) || memcmp(name + nameLength - 11, ":inContext:", 11))
|
|
return;
|
|
// Check for [ id, SEL, <type>, <contextType> ]
|
|
if (method_getNumberOfArguments(method) != 4)
|
|
return;
|
|
char idType[3];
|
|
// Check 2nd argument type is "@"
|
|
{
|
|
auto secondType = adoptSystem<char[]>(method_copyArgumentType(method, 3));
|
|
if (strcmp(secondType.get(), "@") != 0)
|
|
return;
|
|
}
|
|
// Check result type is also "@"
|
|
method_getReturnType(method, idType, 3);
|
|
if (strcmp(idType, "@") != 0)
|
|
return;
|
|
{
|
|
auto type = adoptSystem<char[]>(method_copyArgumentType(method, 2));
|
|
structHandlers->add(StringImpl::create(type.get()), (StructTagHandler) { selector, 0 });
|
|
}
|
|
});
|
|
|
|
// Step 2: find all to<Foo> instance methods in JSValue.
|
|
forEachMethodInClass([JSValue class], ^(Method method){
|
|
SEL selector = method_getName(method);
|
|
const char* name = sel_getName(selector);
|
|
size_t nameLength = strlen(name);
|
|
// Check for to<Foo>
|
|
if (nameLength < toXLength || memcmp(name, "to", 2))
|
|
return;
|
|
// Check for [ id, SEL ]
|
|
if (method_getNumberOfArguments(method) != 2)
|
|
return;
|
|
// Try to find a matching valueWith<Foo>:context: method.
|
|
auto type = adoptSystem<char[]>(method_copyReturnType(method));
|
|
StructHandlers::iterator iter = structHandlers->find(type.get());
|
|
if (iter == structHandlers->end())
|
|
return;
|
|
StructTagHandler& handler = iter->value;
|
|
|
|
// check that strlen(<foo>) == strlen(<Foo>)
|
|
const char* valueWithName = sel_getName(handler.typeToValueSEL);
|
|
size_t valueWithLength = strlen(valueWithName);
|
|
if (valueWithLength - valueWithXinContextLength != nameLength - toXLength)
|
|
return;
|
|
// Check that <Foo> == <Foo>
|
|
if (memcmp(valueWithName + 9, name + 2, nameLength - toXLength - 1))
|
|
return;
|
|
handler.valueToTypeSEL = selector;
|
|
});
|
|
|
|
// Step 3: clean up - remove entries where we found prospective valueWith<Foo>:inContext: conversions, but no matching to<Foo> methods.
|
|
typedef HashSet<String> RemoveSet;
|
|
RemoveSet removeSet;
|
|
for (StructHandlers::iterator iter = structHandlers->begin(); iter != structHandlers->end(); ++iter) {
|
|
StructTagHandler& handler = iter->value;
|
|
if (!handler.valueToTypeSEL)
|
|
removeSet.add(iter->key);
|
|
}
|
|
|
|
for (RemoveSet::iterator iter = removeSet.begin(); iter != removeSet.end(); ++iter)
|
|
structHandlers->remove(*iter);
|
|
|
|
return structHandlers;
|
|
}
|
|
|
|
static StructTagHandler* handerForStructTag(const char* encodedType)
|
|
{
|
|
static Lock handerForStructTagLock;
|
|
LockHolder lockHolder(&handerForStructTagLock);
|
|
|
|
static StructHandlers* structHandlers = createStructHandlerMap();
|
|
|
|
StructHandlers::iterator iter = structHandlers->find(encodedType);
|
|
if (iter == structHandlers->end())
|
|
return 0;
|
|
return &iter->value;
|
|
}
|
|
|
|
+ (SEL)selectorForStructToValue:(const char *)structTag
|
|
{
|
|
StructTagHandler* handler = handerForStructTag(structTag);
|
|
return handler ? handler->typeToValueSEL : nil;
|
|
}
|
|
|
|
+ (SEL)selectorForValueToStruct:(const char *)structTag
|
|
{
|
|
StructTagHandler* handler = handerForStructTag(structTag);
|
|
return handler ? handler->valueToTypeSEL : nil;
|
|
}
|
|
|
|
NSInvocation *typeToValueInvocationFor(const char* encodedType)
|
|
{
|
|
SEL selector = [JSValue selectorForStructToValue:encodedType];
|
|
if (!selector)
|
|
return 0;
|
|
|
|
const char* methodTypes = method_getTypeEncoding(class_getClassMethod([JSValue class], selector));
|
|
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:methodTypes]];
|
|
[invocation setSelector:selector];
|
|
return invocation;
|
|
}
|
|
|
|
NSInvocation *valueToTypeInvocationFor(const char* encodedType)
|
|
{
|
|
SEL selector = [JSValue selectorForValueToStruct:encodedType];
|
|
if (!selector)
|
|
return 0;
|
|
|
|
const char* methodTypes = method_getTypeEncoding(class_getInstanceMethod([JSValue class], selector));
|
|
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:methodTypes]];
|
|
[invocation setSelector:selector];
|
|
return invocation;
|
|
}
|
|
|
|
@end
|
|
|
|
#endif
|