--- /dev/null
+/*
+ * 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. ``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.
+ */
+
+#ifndef APICallbackFunction_h
+#define APICallbackFunction_h
+
+#include "APICast.h"
+#include "APIShims.h"
+#include "Error.h"
+#include "JSCallbackConstructor.h"
+#include <wtf/Vector.h>
+
+namespace JSC {
+
+struct APICallbackFunction {
+
+template <typename T> static EncodedJSValue JSC_HOST_CALL call(ExecState*);
+template <typename T> static EncodedJSValue JSC_HOST_CALL construct(ExecState*);
+
+};
+
+template <typename T>
+EncodedJSValue APICallbackFunction::call(ExecState* exec)
+{
+ JSContextRef execRef = toRef(exec);
+ JSObjectRef functionRef = toRef(exec->callee());
+ JSObjectRef thisObjRef = toRef(exec->hostThisValue().toThisObject(exec));
+
+ int argumentCount = static_cast<int>(exec->argumentCount());
+ Vector<JSValueRef, 16> arguments;
+ arguments.reserveInitialCapacity(argumentCount);
+ for (int i = 0; i < argumentCount; i++)
+ arguments.uncheckedAppend(toRef(exec, exec->argument(i)));
+
+ JSValueRef exception = 0;
+ JSValueRef result;
+ {
+ APICallbackShim callbackShim(exec);
+ result = jsCast<T*>(toJS(functionRef))->functionCallback()(execRef, functionRef, thisObjRef, argumentCount, arguments.data(), &exception);
+ }
+ if (exception)
+ throwError(exec, toJS(exec, exception));
+
+ // result must be a valid JSValue.
+ if (!result)
+ return JSValue::encode(jsUndefined());
+
+ return JSValue::encode(toJS(exec, result));
+}
+
+template <typename T>
+EncodedJSValue JSC_HOST_CALL APICallbackFunction::construct(ExecState* exec)
+{
+ JSObject* constructor = exec->callee();
+ JSContextRef ctx = toRef(exec);
+ JSObjectRef constructorRef = toRef(constructor);
+
+ JSObjectCallAsConstructorCallback callback = jsCast<T*>(constructor)->constructCallback();
+ if (callback) {
+ size_t argumentCount = exec->argumentCount();
+ Vector<JSValueRef, 16> arguments;
+ arguments.reserveInitialCapacity(argumentCount);
+ for (size_t i = 0; i < argumentCount; ++i)
+ arguments.uncheckedAppend(toRef(exec, exec->argument(i)));
+
+ JSValueRef exception = 0;
+ JSObjectRef result;
+ {
+ APICallbackShim callbackShim(exec);
+ result = callback(ctx, constructorRef, argumentCount, arguments.data(), &exception);
+ }
+ if (exception)
+ throwError(exec, toJS(exec, exception));
+ // result must be a valid JSValue.
+ if (!result)
+ return throwVMTypeError(exec);
+ return JSValue::encode(toJS(result));
+ }
+
+ return JSValue::encode(toJS(JSObjectMake(ctx, jsCast<JSCallbackConstructor*>(constructor)->classRef(), 0)));
+}
+
+} // namespace JSC
+
+#endif // APICallbackFunction_h
#include "config.h"
#include "JSCallbackConstructor.h"
-#include "APIShims.h"
+#include "APICallbackFunction.h"
#include "APICast.h"
+#include "APIShims.h"
#include "Error.h"
#include "JSGlobalObject.h"
#include "JSLock.h"
static_cast<JSCallbackConstructor*>(cell)->JSCallbackConstructor::~JSCallbackConstructor();
}
-static EncodedJSValue JSC_HOST_CALL constructJSCallback(ExecState* exec)
-{
- JSObject* constructor = exec->callee();
- JSContextRef ctx = toRef(exec);
- JSObjectRef constructorRef = toRef(constructor);
-
- JSObjectCallAsConstructorCallback callback = jsCast<JSCallbackConstructor*>(constructor)->callback();
- if (callback) {
- size_t argumentCount = exec->argumentCount();
- Vector<JSValueRef, 16> arguments;
- arguments.reserveInitialCapacity(argumentCount);
- for (size_t i = 0; i < argumentCount; ++i)
- arguments.uncheckedAppend(toRef(exec, exec->argument(i)));
-
- JSValueRef exception = 0;
- JSObjectRef result;
- {
- APICallbackShim callbackShim(exec);
- result = callback(ctx, constructorRef, argumentCount, arguments.data(), &exception);
- }
- if (exception)
- throwError(exec, toJS(exec, exception));
- // result must be a valid JSValue.
- if (!result)
- return throwVMTypeError(exec);
- return JSValue::encode(toJS(result));
- }
-
- return JSValue::encode(toJS(JSObjectMake(ctx, jsCast<JSCallbackConstructor*>(constructor)->classRef(), 0)));
-}
-
ConstructType JSCallbackConstructor::getConstructData(JSCell*, ConstructData& constructData)
{
- constructData.native.function = constructJSCallback;
+ constructData.native.function = APICallbackFunction::construct<JSCallbackConstructor>;
return ConstructTypeHost;
}
static const unsigned StructureFlags = ImplementsHasInstance | JSObject::StructureFlags;
private:
+ friend struct APICallbackFunction;
+
static ConstructType getConstructData(JSCell*, ConstructData&);
+ JSObjectCallAsConstructorCallback constructCallback() { return m_callback; }
+
JSClassRef m_class;
JSObjectCallAsConstructorCallback m_callback;
};
#include "config.h"
#include "JSCallbackFunction.h"
-#include "APIShims.h"
+#include "APICallbackFunction.h"
#include "APICast.h"
+#include "APIShims.h"
#include "CodeBlock.h"
#include "Error.h"
#include "ExceptionHelpers.h"
return function;
}
-EncodedJSValue JSCallbackFunction::call(ExecState* exec)
-{
- JSContextRef execRef = toRef(exec);
- JSObjectRef functionRef = toRef(exec->callee());
- JSObjectRef thisObjRef = toRef(exec->hostThisValue().toThisObject(exec));
-
- size_t argumentCount = exec->argumentCount();
- Vector<JSValueRef, 16> arguments;
- arguments.reserveInitialCapacity(argumentCount);
- for (size_t i = 0; i < argumentCount; ++i)
- arguments.uncheckedAppend(toRef(exec, exec->argument(i)));
-
- JSValueRef exception = 0;
- JSValueRef result;
- {
- APICallbackShim callbackShim(exec);
- result = jsCast<JSCallbackFunction*>(toJS(functionRef))->m_callback(execRef, functionRef, thisObjRef, argumentCount, arguments.data(), &exception);
- }
- if (exception)
- throwError(exec, toJS(exec, exception));
-
- // result must be a valid JSValue.
- if (!result)
- return JSValue::encode(jsUndefined());
-
- return JSValue::encode(toJS(exec, result));
-}
-
CallType JSCallbackFunction::getCallData(JSCell*, CallData& callData)
{
- callData.native.function = call;
+ callData.native.function = APICallbackFunction::call<JSCallbackFunction>;
return CallTypeHost;
}
namespace JSC {
class JSCallbackFunction : public InternalFunction {
-protected:
- JSCallbackFunction(JSGlobalObject*, Structure*, JSObjectCallAsFunctionCallback);
- void finishCreation(VM&, const String& name);
-
+ friend struct APICallbackFunction;
public:
typedef InternalFunction Base;
return Structure::create(vm, globalObject, proto, TypeInfo(ObjectType, StructureFlags), &s_info);
}
-protected:
+private:
+ JSCallbackFunction(JSGlobalObject*, Structure*, JSObjectCallAsFunctionCallback);
+ void finishCreation(VM&, const String& name);
+
static CallType getCallData(JSCell*, CallData&);
-private:
- static EncodedJSValue JSC_HOST_CALL call(ExecState*);
+ JSObjectCallAsFunctionCallback functionCallback() { return m_callback; }
JSObjectCallAsFunctionCallback m_callback;
};
return &jsManagedValueHandleOwner;
}
+class WeakValueRef {
+public:
+ WeakValueRef()
+ : m_tag(NotSet)
+ {
+ }
+
+ ~WeakValueRef()
+ {
+ clear();
+ }
+
+ void clear()
+ {
+ switch (m_tag) {
+ case NotSet:
+ return;
+ case Primitive:
+ u.m_primitive = JSC::JSValue();
+ return;
+ case Object:
+ u.m_object.clear();
+ return;
+ case String:
+ u.m_string.clear();
+ return;
+ }
+ RELEASE_ASSERT_NOT_REACHED();
+ }
+
+ bool isClear() const
+ {
+ switch (m_tag) {
+ case NotSet:
+ return true;
+ case Primitive:
+ return !u.m_primitive;
+ case Object:
+ return !u.m_object;
+ case String:
+ return !u.m_string;
+ }
+ RELEASE_ASSERT_NOT_REACHED();
+ }
+
+ bool isSet() const { return m_tag != NotSet; }
+ bool isPrimitive() const { return m_tag == Primitive; }
+ bool isObject() const { return m_tag == Object; }
+ bool isString() const { return m_tag == String; }
+
+ void setPrimitive(JSC::JSValue primitive)
+ {
+ ASSERT(!isSet());
+ ASSERT(!u.m_primitive);
+ ASSERT(primitive.isPrimitive());
+ m_tag = Primitive;
+ u.m_primitive = primitive;
+ }
+
+ void setObject(JSC::JSObject* object, void* context)
+ {
+ ASSERT(!isSet());
+ ASSERT(!u.m_object);
+ m_tag = Object;
+ JSC::Weak<JSC::JSObject> weak(object, managedValueHandleOwner(), context);
+ u.m_object.swap(weak);
+ }
+
+ void setString(JSC::JSString* string, void* context)
+ {
+ ASSERT(!isSet());
+ ASSERT(!u.m_object);
+ m_tag = String;
+ JSC::Weak<JSC::JSString> weak(string, managedValueHandleOwner(), context);
+ u.m_string.swap(weak);
+ }
+
+ JSC::JSObject* object()
+ {
+ ASSERT(isObject());
+ return u.m_object.get();
+ }
+
+ JSC::JSValue primitive()
+ {
+ ASSERT(isPrimitive());
+ return u.m_primitive;
+ }
+
+ JSC::JSString* string()
+ {
+ ASSERT(isString());
+ return u.m_string.get();
+ }
+
+private:
+ enum WeakTypeTag { NotSet, Primitive, Object, String };
+ WeakTypeTag m_tag;
+ union WeakValueUnion {
+ public:
+ WeakValueUnion ()
+ : m_primitive(JSC::JSValue())
+ {
+ }
+
+ ~WeakValueUnion()
+ {
+ ASSERT(!m_primitive);
+ }
+
+ JSC::JSValue m_primitive;
+ JSC::Weak<JSC::JSObject> m_object;
+ JSC::Weak<JSC::JSString> m_string;
+ } u;
+};
+
@implementation JSManagedValue {
- JSC::Weak<JSC::JSObject> m_value;
+ JSC::Weak<JSC::JSGlobalObject> m_globalObject;
+ WeakValueRef m_weakValue;
}
+ (JSManagedValue *)managedValueWithValue:(JSValue *)value
if (!self)
return nil;
- if (!value || !JSValueIsObject([value.context JSGlobalContextRef], [value JSValueRef])) {
- JSC::Weak<JSC::JSObject> weak;
- m_value.swap(weak);
- } else {
- JSC::JSObject* object = toJS(const_cast<OpaqueJSValue*>([value JSValueRef]));
- JSC::Weak<JSC::JSObject> weak(object, managedValueHandleOwner(), self);
- m_value.swap(weak);
- }
-
+ if (!value)
+ return self;
+
+ JSC::ExecState* exec = toJS([value.context JSGlobalContextRef]);
+ JSC::JSGlobalObject* globalObject = exec->lexicalGlobalObject();
+ JSC::Weak<JSC::JSGlobalObject> weak(globalObject, managedValueHandleOwner(), self);
+ m_globalObject.swap(weak);
+
+ JSC::JSValue jsValue = toJS(exec, [value JSValueRef]);
+ if (jsValue.isObject())
+ m_weakValue.setObject(JSC::jsCast<JSC::JSObject*>(jsValue.asCell()), self);
+ else if (jsValue.isString())
+ m_weakValue.setString(JSC::jsCast<JSC::JSString*>(jsValue.asCell()), self);
+ else
+ m_weakValue.setPrimitive(jsValue);
return self;
}
- (JSValue *)value
{
- if (!m_value)
+ if (!m_globalObject)
+ return nil;
+ if (m_weakValue.isClear())
return nil;
- JSC::JSObject* object = m_value.get();
- JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(object->structure()->globalObject()->globalExec())];
- return [JSValue valueWithJSValueRef:toRef(object) inContext:context];
+ JSC::ExecState* exec = m_globalObject->globalExec();
+ JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(exec)];
+ JSC::JSValue value;
+ if (m_weakValue.isPrimitive())
+ value = m_weakValue.primitive();
+ else if (m_weakValue.isString())
+ value = m_weakValue.string();
+ else
+ value = m_weakValue.object();
+ return [JSValue valueWithJSValueRef:toRef(exec, value) inContext:context];
}
- (void)disconnectValue
{
- m_value.clear();
+ m_globalObject.clear();
+ m_weakValue.clear();
}
@end
#import "ObjcRuntimeExtras.h"
#import "Operations.h"
#import "JSCJSValue.h"
+#import "StrongInlines.h"
#import <wtf/HashMap.h>
#import <wtf/HashSet.h>
+#import <wtf/ObjcRuntimeExtras.h>
#import <wtf/Vector.h>
#import <wtf/TCSpinLock.h>
#import <wtf/text/WTFString.h>
JSGlobalContextRef m_context;
HashMap<JSValueRef, id> m_objectMap;
Vector<Task> m_worklist;
+ Vector<JSC::Strong<JSC::Unknown>> m_jsValues;
};
inline id JSContainerConvertor::convert(JSValueRef value)
void JSContainerConvertor::add(Task task)
{
+ JSC::ExecState* exec = toJS(m_context);
+ m_jsValues.append(JSC::Strong<JSC::Unknown>(exec->vm(), toJSForGC(exec, task.js)));
m_objectMap.add(task.js, task.objc);
if (task.type != ContainerNone)
m_worklist.append(task);
static id containerValueToObject(JSGlobalContextRef context, JSContainerConvertor::Task task)
{
ASSERT(task.type != ContainerNone);
+ JSC::APIEntryShim entryShim(toJS(context));
JSContainerConvertor convertor(context);
convertor.add(task);
ASSERT(!convertor.isWorkListEmpty());
return nil;
}
- NSString *stringNS = [(NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsstring) autorelease];
+ NSString *stringNS = HardAutorelease(JSStringCopyCFString(kCFAllocatorDefault, jsstring));
JSStringRelease(jsstring);
return stringNS;
}
JSContext *m_context;
HashMap<id, JSValueRef> m_objectMap;
Vector<Task> m_worklist;
+ Vector<JSC::Strong<JSC::Unknown>> m_jsValues;
};
JSValueRef ObjcContainerConvertor::convert(id object)
void ObjcContainerConvertor::add(ObjcContainerConvertor::Task task)
{
+ JSC::ExecState* exec = toJS(m_context.JSGlobalContextRef);
+ m_jsValues.append(JSC::Strong<JSC::Unknown>(exec->vm(), toJSForGC(exec, task.js)));
m_objectMap.add(task.objc, task.js);
if (task.type != ContainerNone)
m_worklist.append(task);
if (task.type == ContainerNone)
return task.js;
+ JSC::APIEntryShim entryShim(toJS(contextRef));
ObjcContainerConvertor convertor(context);
convertor.add(task);
ASSERT(!convertor.isWorkListEmpty());
#import "WeakGCMap.h"
#import <wtf/TCSpinLock.h>
#import <wtf/Vector.h>
+#import <wtf/HashSet.h>
+
+#if OS(DARWIN)
+#include <mach-o/dyld.h>
+
+static const int32_t webkitFirstVersionWithInitConstructorSupport = 0x2193302; // 537.51.2
+#endif
@class JSObjCClassInfo;
return result;
}
+static bool constructorHasInstance(JSContextRef ctx, JSObjectRef constructorRef, JSValueRef possibleInstance, JSValueRef*)
+{
+ JSC::ExecState* exec = toJS(ctx);
+ JSC::APIEntryShim entryShim(exec);
+
+ JSC::JSObject* constructor = toJS(constructorRef);
+ JSC::JSValue instance = toJS(exec, possibleInstance);
+ return JSC::JSObject::defaultHasInstance(exec, instance, constructor->get(exec, exec->propertyNames().prototype));
+}
+
static JSObjectRef makeWrapper(JSContextRef ctx, JSClassRef jsClass, id wrappedObject)
{
JSC::ExecState* exec = toJS(ctx);
return [JSValue valueWithJSValueRef:result inContext:context];
}
+static JSValue *constructorWithCustomBrand(JSContext *context, NSString *brand, Class cls)
+{
+ JSClassDefinition definition;
+ definition = kJSClassDefinitionEmpty;
+ definition.className = [brand UTF8String];
+ definition.hasInstance = constructorHasInstance;
+ JSClassRef classRef = JSClassCreate(&definition);
+ JSObjectRef result = makeWrapper([context JSGlobalContextRef], classRef, cls);
+ JSClassRelease(classRef);
+ return [JSValue valueWithJSValueRef:result inContext:context];
+}
+
// Look for @optional properties in the prototype containing a selector to property
// name mapping, separated by a __JS_EXPORT_AS__ delimiter.
static NSMutableDictionary *createRenameMap(Protocol *protocol, BOOL isInstanceMethod)
forEachMethodInProtocol(protocol, YES, isInstanceMethod, ^(SEL sel, const char* types){
const char* nameCStr = sel_getName(sel);
NSString *name = @(nameCStr);
+ // Don't copy over init family methods because we handle those specially
+ // for the purposes of hooking up the constructor correctly.
+ if ([name hasPrefix:@"init"])
+ return;
+
if (accessorMethods && accessorMethods[name]) {
JSObjectRef method = objCCallbackFunctionForMethod(context, objcClass, protocol, isInstanceMethod, sel, types);
if (!method)
[super dealloc];
}
+static JSValue *allocateConstructorForCustomClass(JSContext *context, const char* className, Class cls)
+{
+#if OS(DARWIN)
+ if (NSVersionOfLinkTimeLibrary("JavaScriptCore") < webkitFirstVersionWithInitConstructorSupport)
+ return constructorWithCustomBrand(context, [NSString stringWithFormat:@"%sConstructor", className], cls);
+#endif
+ // For each protocol that the class implements, gather all of the init family methods into a hash table.
+ __block HashMap<String, Protocol *> initTable;
+ Protocol *exportProtocol = getJSExportProtocol();
+ for (Class currentClass = cls; currentClass; currentClass = class_getSuperclass(currentClass)) {
+ forEachProtocolImplementingProtocol(currentClass, exportProtocol, ^(Protocol *protocol) {
+ forEachMethodInProtocol(protocol, YES, YES, ^(SEL selector, const char*) {
+ const char* name = sel_getName(selector);
+ if (![@(name) hasPrefix:@"init"])
+ return;
+ initTable.set(name, protocol);
+ });
+ });
+ }
+
+ for (Class currentClass = cls; currentClass; currentClass = class_getSuperclass(currentClass)) {
+ __block unsigned numberOfInitsFound = 0;
+ __block SEL initMethod = 0;
+ __block Protocol *initProtocol = 0;
+ __block const char* types = 0;
+ forEachMethodInClass(currentClass, ^(Method method) {
+ SEL selector = method_getName(method);
+ const char* name = sel_getName(selector);
+ auto iter = initTable.find(name);
+
+ if (iter == initTable.end())
+ return;
+
+ numberOfInitsFound++;
+ initMethod = selector;
+ initProtocol = iter->value;
+ types = method_getTypeEncoding(method);
+ });
+
+ if (!numberOfInitsFound)
+ continue;
+
+ if (numberOfInitsFound > 1) {
+ NSLog(@"ERROR: Class %@ exported more than one init family method via JSExport. Class %@ will not have a callable JavaScript constructor function.", cls, cls);
+ break;
+ }
+
+ JSObjectRef method = objCCallbackFunctionForInit(context, cls, initProtocol, initMethod, types);
+ return [JSValue valueWithJSValueRef:method inContext:context];
+ }
+ return constructorWithCustomBrand(context, [NSString stringWithFormat:@"%sConstructor", className], cls);
+}
+
- (void)allocateConstructorAndPrototypeWithSuperClassInfo:(JSObjCClassInfo*)superClassInfo
{
ASSERT(!m_constructor || !m_prototype);
if (m_constructor)
constructor = [JSValue valueWithJSValueRef:toRef(m_constructor.get()) inContext:m_context];
else
- constructor = objectWithCustomBrand(m_context, [NSString stringWithFormat:@"%sConstructor", className], m_class);
+ constructor = allocateConstructorForCustomClass(m_context, className, m_class);
JSContextRef cContext = [m_context JSGlobalContextRef];
m_prototype = toJS(JSValueToObject(cContext, valueInternalValue(prototype), 0));
ASSERT([object isKindOfClass:m_class]);
ASSERT(m_block == [object isKindOfClass:getNSBlockClass()]);
if (m_block) {
- if (JSObjectRef method = objCCallbackFunctionForBlock(m_context, object))
- return [JSValue valueWithJSValueRef:method inContext:m_context];
+ if (JSObjectRef method = objCCallbackFunctionForBlock(m_context, object)) {
+ JSValue *constructor = [JSValue valueWithJSValueRef:method inContext:m_context];
+ JSValue *prototype = [JSValue valueWithNewObjectInContext:m_context];
+ putNonEnumerable(constructor, @"prototype", prototype);
+ putNonEnumerable(prototype, @"constructor", constructor);
+ return constructor;
+ }
}
if (!m_prototype)
ASSERT(!exception);
if (toJS(object)->inherits(&JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::s_info))
return (id)JSC::jsCast<JSC::JSAPIWrapperObject*>(toJS(object))->wrappedObject();
- if (id target = tryUnwrapBlock(object))
+ if (id target = tryUnwrapConstructor(object))
return target;
return nil;
}
#if defined(__OBJC__)
JSObjectRef objCCallbackFunctionForMethod(JSContext *, Class, Protocol *, BOOL isInstanceMethod, SEL, const char* types);
JSObjectRef objCCallbackFunctionForBlock(JSContext *, id);
+JSObjectRef objCCallbackFunctionForInit(JSContext *, Class, Protocol *, SEL, const char* types);
-id tryUnwrapBlock(JSObjectRef);
+id tryUnwrapConstructor(JSObjectRef);
#endif
namespace JSC {
class ObjCCallbackFunctionImpl;
-class ObjCCallbackFunction : public JSCallbackFunction {
+class ObjCCallbackFunction : public InternalFunction {
+ friend struct APICallbackFunction;
public:
- typedef JSCallbackFunction Base;
+ typedef InternalFunction Base;
- static ObjCCallbackFunction* create(ExecState*, JSGlobalObject*, const String& name, PassOwnPtr<ObjCCallbackFunctionImpl>);
+ static ObjCCallbackFunction* create(VM&, JSGlobalObject*, const String& name, PassOwnPtr<ObjCCallbackFunctionImpl>);
static void destroy(JSCell*);
static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
static JS_EXPORTDATA const ClassInfo s_info;
- ObjCCallbackFunctionImpl* impl() { return m_impl.get(); }
+ ObjCCallbackFunctionImpl* impl() const { return m_impl.get(); }
protected:
- ObjCCallbackFunction(JSGlobalObject*, JSObjectCallAsFunctionCallback, PassOwnPtr<ObjCCallbackFunctionImpl>);
+ ObjCCallbackFunction(VM&, JSGlobalObject*, JSObjectCallAsFunctionCallback, JSObjectCallAsConstructorCallback, PassOwnPtr<ObjCCallbackFunctionImpl>);
private:
+ static CallType getCallData(JSCell*, CallData&);
+ static ConstructType getConstructData(JSCell*, ConstructData&);
+
+ JSObjectCallAsFunctionCallback functionCallback() { return m_functionCallback; }
+ JSObjectCallAsConstructorCallback constructCallback() { return m_constructCallback; }
+
+ JSObjectCallAsFunctionCallback m_functionCallback;
+ JSObjectCallAsConstructorCallback m_constructCallback;
OwnPtr<ObjCCallbackFunctionImpl> m_impl;
};
#if JSC_OBJC_API_ENABLED
+#import "APICallbackFunction.h"
#import "APICast.h"
#import "APIShims.h"
#import "Error.h"
};
enum CallbackType {
+ CallbackInitMethod,
CallbackInstanceMethod,
CallbackClassMethod,
CallbackBlock
, m_arguments(arguments)
, m_result(result)
{
- ASSERT(type != CallbackInstanceMethod || instanceClass);
+ ASSERT((type != CallbackInstanceMethod && type != CallbackInitMethod) || instanceClass);
}
~ObjCCallbackFunctionImpl()
{
- if (m_type != CallbackInstanceMethod)
+ // We need to explicity release the target since we didn't call
+ // -retainArguments on m_invocation (and we don't want to do so).
+ if (m_type == CallbackBlock || m_type == CallbackClassMethod)
[[m_invocation.get() target] release];
[m_instanceClass release];
}
return m_type == CallbackBlock ? [m_invocation target] : nil;
}
+ id wrappedConstructor()
+ {
+ switch (m_type) {
+ case CallbackBlock:
+ return [m_invocation target];
+ case CallbackInitMethod:
+ return m_instanceClass;
+ default:
+ return nil;
+ }
+ }
+
+ bool isConstructible()
+ {
+ return !!wrappedBlock() || m_type == CallbackInitMethod;
+ }
+
+ String name();
+
private:
WeakContextRef m_context;
CallbackType m_type;
return result;
}
+static JSObjectRef objCCallbackFunctionCallAsConstructor(JSContextRef callerContext, JSObjectRef constructor, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+ JSC::APIEntryShim entryShim(toJS(callerContext));
+
+ ObjCCallbackFunction* callback = static_cast<ObjCCallbackFunction*>(toJS(constructor));
+ ObjCCallbackFunctionImpl* impl = callback->impl();
+ JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(toJS(callerContext)->lexicalGlobalObject()->globalExec())];
+
+ CallbackData callbackData;
+ JSValueRef result;
+ @autoreleasepool {
+ [context beginCallbackWithData:&callbackData thisValue:nil argumentCount:argumentCount arguments:arguments];
+ result = impl->call(context, NULL, argumentCount, arguments, exception);
+ if (context.exception)
+ *exception = valueInternalValue(context.exception);
+ [context endCallbackWithData:&callbackData];
+ }
+
+ JSGlobalContextRef contextRef = [context JSGlobalContextRef];
+ if (*exception)
+ return 0;
+
+ if (!JSValueIsObject(contextRef, result)) {
+ *exception = toRef(JSC::createTypeError(toJS(contextRef), "Objective-C blocks called as constructors must return an object."));
+ return 0;
+ }
+ return (JSObjectRef)result;
+}
+
const JSC::ClassInfo ObjCCallbackFunction::s_info = { "CallbackFunction", &Base::s_info, 0, 0, CREATE_METHOD_TABLE(ObjCCallbackFunction) };
-ObjCCallbackFunction::ObjCCallbackFunction(JSC::JSGlobalObject* globalObject, JSObjectCallAsFunctionCallback callback, PassOwnPtr<ObjCCallbackFunctionImpl> impl)
- : Base(globalObject, globalObject->objcCallbackFunctionStructure(), callback)
+ObjCCallbackFunction::ObjCCallbackFunction(JSC::VM&, JSC::JSGlobalObject* globalObject, JSObjectCallAsFunctionCallback functionCallback, JSObjectCallAsConstructorCallback constructCallback, PassOwnPtr<ObjCCallbackFunctionImpl> impl)
+ : Base(globalObject, globalObject->objcCallbackFunctionStructure())
+ , m_functionCallback(functionCallback)
+ , m_constructCallback(constructCallback)
, m_impl(impl)
{
}
-ObjCCallbackFunction* ObjCCallbackFunction::create(JSC::ExecState* exec, JSC::JSGlobalObject* globalObject, const String& name, PassOwnPtr<ObjCCallbackFunctionImpl> impl)
+ObjCCallbackFunction* ObjCCallbackFunction::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, const String& name, PassOwnPtr<ObjCCallbackFunctionImpl> impl)
{
- ObjCCallbackFunction* function = new (NotNull, allocateCell<ObjCCallbackFunction>(*exec->heap())) ObjCCallbackFunction(globalObject, objCCallbackFunctionCallAsFunction, impl);
- function->finishCreation(exec->vm(), name);
+ ObjCCallbackFunction* function = new (NotNull, allocateCell<ObjCCallbackFunction>(vm.heap)) ObjCCallbackFunction(vm, globalObject, objCCallbackFunctionCallAsFunction, objCCallbackFunctionCallAsConstructor, impl);
+ function->finishCreation(vm, name);
return function;
}
static_cast<ObjCCallbackFunction*>(cell)->ObjCCallbackFunction::~ObjCCallbackFunction();
}
+
+CallType ObjCCallbackFunction::getCallData(JSCell*, CallData& callData)
+{
+ callData.native.function = APICallbackFunction::call<ObjCCallbackFunction>;
+ return CallTypeHost;
+}
+
+ConstructType ObjCCallbackFunction::getConstructData(JSCell* cell, ConstructData& constructData)
+{
+ ObjCCallbackFunction* callback = jsCast<ObjCCallbackFunction*>(cell);
+ if (!callback->impl()->isConstructible())
+ return Base::getConstructData(cell, constructData);
+ constructData.native.function = APICallbackFunction::construct<ObjCCallbackFunction>;
+ return ConstructTypeHost;
+}
+
+String ObjCCallbackFunctionImpl::name()
+{
+ if (m_type == CallbackInitMethod)
+ return class_getName(m_instanceClass);
+ // FIXME: Maybe we could support having the selector as the name of the non-init
+ // functions to make it a bit more user-friendly from the JS side?
+ return "";
+}
+
JSValueRef ObjCCallbackFunctionImpl::call(JSContext *context, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
JSGlobalContextRef contextRef = [context JSGlobalContextRef];
+ id target;
size_t firstArgument;
switch (m_type) {
+ case CallbackInitMethod: {
+ RELEASE_ASSERT(!thisObject);
+ target = [m_instanceClass alloc];
+ if (!target || ![target isKindOfClass:m_instanceClass]) {
+ *exception = toRef(JSC::createTypeError(toJS(contextRef), "self type check failed for Objective-C instance method"));
+ return JSValueMakeUndefined(contextRef);
+ }
+ [m_invocation setTarget:target];
+ firstArgument = 2;
+ break;
+ }
case CallbackInstanceMethod: {
- id target = tryUnwrapObjcObject(contextRef, thisObject);
+ target = tryUnwrapObjcObject(contextRef, thisObject);
if (!target || ![target isKindOfClass:m_instanceClass]) {
*exception = toRef(JSC::createTypeError(toJS(contextRef), "self type check failed for Objective-C instance method"));
return JSValueMakeUndefined(contextRef);
}
[m_invocation setTarget:target];
+ firstArgument = 2;
+ break;
}
- // fallthrough - firstArgument for CallbackInstanceMethod is also 2!
case CallbackClassMethod:
firstArgument = 2;
break;
[m_invocation invoke];
- return m_result->get(m_invocation.get(), context, exception);
+ JSValueRef result = m_result->get(m_invocation.get(), context, exception);
+
+ // Balance our call to -alloc with a call to -autorelease. We have to do this after calling -init
+ // because init family methods are allowed to release the allocated object and return something
+ // else in its place.
+ if (m_type == CallbackInitMethod) {
+ id objcResult = tryUnwrapObjcObject(contextRef, result);
+ if (objcResult)
+ [objcResult autorelease];
+ }
+
+ return result;
}
} // namespace JSC
return nil;
switch (type) {
+ case CallbackInitMethod:
case CallbackInstanceMethod:
case CallbackClassMethod:
// Methods are passed two implicit arguments - (id)self, and the selector.
JSC::ExecState* exec = toJS([context JSGlobalContextRef]);
JSC::APIEntryShim shim(exec);
OwnPtr<JSC::ObjCCallbackFunctionImpl> impl = adoptPtr(new JSC::ObjCCallbackFunctionImpl(context, invocation, type, instanceClass, arguments.release(), result.release()));
- // FIXME: Maybe we could support having the selector as the name of the function to make it a bit more user-friendly from the JS side?
- return toRef(JSC::ObjCCallbackFunction::create(exec, exec->lexicalGlobalObject(), "", impl.release()));
+ return toRef(JSC::ObjCCallbackFunction::create(exec->vm(), exec->lexicalGlobalObject(), impl->name(), impl.release()));
+}
+
+JSObjectRef objCCallbackFunctionForInit(JSContext *context, Class cls, Protocol *protocol, SEL sel, const char* types)
+{
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:types]];
+ [invocation setSelector:sel];
+ return objCCallbackFunctionForInvocation(context, invocation, CallbackInitMethod, cls, _protocol_getMethodTypeEncoding(protocol, sel, YES, YES));
}
JSObjectRef objCCallbackFunctionForMethod(JSContext *context, Class cls, Protocol *protocol, BOOL isInstanceMethod, SEL sel, const char* types)
{
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:types]];
[invocation setSelector:sel];
+ // We need to retain the target Class because m_invocation doesn't retain it
+ // by default (and we don't want it to).
if (!isInstanceMethod)
- [invocation setTarget:cls];
+ [invocation setTarget:[cls retain]];
return objCCallbackFunctionForInvocation(context, invocation, isInstanceMethod ? CallbackInstanceMethod : CallbackClassMethod, isInstanceMethod ? cls : nil, _protocol_getMethodTypeEncoding(protocol, sel, YES, isInstanceMethod));
}
return 0;
const char* signature = _Block_signature(target);
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:signature]];
+
+ // We don't want to use -retainArguments because that leaks memory. Arguments
+ // would be retained indefinitely between invocations of the callback.
+ // Additionally, we copy the target because we want the block to stick around
+ // until the ObjCCallbackFunctionImpl is destroyed.
[invocation setTarget:[target copy]];
+
return objCCallbackFunctionForInvocation(context, invocation, CallbackBlock, nil, signature);
}
-id tryUnwrapBlock(JSObjectRef object)
+id tryUnwrapConstructor(JSObjectRef object)
{
if (!toJS(object)->inherits(&JSC::ObjCCallbackFunction::s_info))
return nil;
- return static_cast<JSC::ObjCCallbackFunction*>(toJS(object))->impl()->wrappedBlock();
+ JSC::ObjCCallbackFunctionImpl* impl = static_cast<JSC::ObjCCallbackFunction*>(toJS(object))->impl();
+ if (!impl->isConstructible())
+ return nil;
+ return impl->wrappedConstructor();
}
#endif
FILE* f = fopen(fileName, "r");
if (!f) {
fprintf(stderr, "Could not open file: %s\n", fileName);
+ free(buffer);
return 0;
}
FILE* f = fopen(fileName, "r");
if (!f) {
fprintf(stderr, "Could not open file: %s\n", fileName);
+ free(buffer);
return 0;
}
#if JSC_OBJC_API_ENABLED
+@interface UnexportedObject : NSObject
+@end
+
+@implementation UnexportedObject
+@end
+
@protocol ParentObject <JSExport>
@end
@end
@protocol TestObject <JSExport>
+- (id)init;
@property int variable;
@property (readonly) int six;
@property CGPoint point;
bool testXYZTested = false;
@protocol TextXYZ <JSExport>
+- (id)initWithString:(NSString*)string;
@property int x;
@property (readonly) int y;
@property (assign) JSValue *onclick;
@synthesize x;
@synthesize y;
@synthesize z;
+- (id)initWithString:(NSString*)string
+{
+ self = [super init];
+ if (!self)
+ return nil;
+
+ NSLog(@"%@", string);
+
+ return self;
+}
- (void)test:(NSString *)message
{
testXYZTested = [message isEqual:@"test"] && x == 13 & y == 4 && z == 5;
@end
+@interface JSCollection : NSObject
+- (void)setValue:(JSValue *)value forKey:(NSString *)key;
+- (JSValue *)valueForKey:(NSString *)key;
+@end
+
+@implementation JSCollection {
+ NSMutableDictionary *_dict;
+}
+- (id)init
+{
+ self = [super init];
+ if (!self)
+ return nil;
+
+ _dict = [[NSMutableDictionary alloc] init];
+
+ return self;
+}
+
+- (void)setValue:(JSValue *)value forKey:(NSString *)key
+{
+ JSManagedValue *oldManagedValue = [_dict objectForKey:key];
+ if (oldManagedValue) {
+ JSValue* oldValue = [oldManagedValue value];
+ if (oldValue)
+ [oldValue.context.virtualMachine removeManagedReference:oldManagedValue withOwner:self];
+ }
+ JSManagedValue *managedValue = [JSManagedValue managedValueWithValue:value];
+ [value.context.virtualMachine addManagedReference:managedValue withOwner:self];
+ [_dict setObject:managedValue forKey:key];
+}
+
+- (JSValue *)valueForKey:(NSString *)key
+{
+ JSManagedValue *managedValue = [_dict objectForKey:key];
+ if (!managedValue)
+ return nil;
+ return [managedValue value];
+}
+@end
+
+@protocol InitA <JSExport>
+- (id)initWithA:(int)a;
+@end
+
+@protocol InitB <JSExport>
+- (id)initWithA:(int)a b:(int)b;
+@end
+
+@interface ClassA : NSObject<InitA>
+@end
+
+@interface ClassB : ClassA<InitB>
+@end
+
+@interface ClassC : ClassB<InitA, InitB>
+@end
+
+@interface ClassD : NSObject<InitA>
+- (id)initWithA:(int)a;
+@end
+
+@interface ClassE : ClassD
+- (id)initWithA:(int)a;
+@end
+
+@implementation ClassA {
+ int _a;
+}
+- (id)initWithA:(int)a
+{
+ self = [super init];
+ if (!self)
+ return nil;
+
+ _a = a;
+
+ return self;
+}
+@end
+
+@implementation ClassB {
+ int _b;
+}
+- (id)initWithA:(int)a b:(int)b
+{
+ self = [super initWithA:a];
+ if (!self)
+ return nil;
+
+ _b = b;
+
+ return self;
+}
+@end
+
+@implementation ClassC {
+ int _c;
+}
+- (id)initWithA:(int)a
+{
+ return [self initWithA:a b:0];
+}
+- (id)initWithA:(int)a b:(int)b
+{
+ self = [super initWithA:a b:b];
+ if (!self)
+ return nil;
+
+ _c = a + b;
+
+ return self;
+}
+@end
+
+@implementation ClassD
+
+- (id)initWithA:(int)a
+{
+ self = nil;
+ return [[ClassE alloc] initWithA:a];
+}
+@end
+
+@implementation ClassE {
+ int _a;
+}
+
+- (id)initWithA:(int)a
+{
+ self = [super init];
+ if (!self)
+ return nil;
+
+ _a = a;
+
+ return self;
+}
+@end
static void checkResult(NSString *description, bool passed)
{
NSLog(@"TEST: \"%@\": %@", description, passed ? @"PASSED" : @"FAILED");
checkResult(@"Check numeric literal", [num isKindOfClass:[NSNumber class]]);
}
+ @autoreleasepool {
+ JSCollection* myPrivateProperties = [[JSCollection alloc] init];
+
+ @autoreleasepool {
+ JSContext* context = [[JSContext alloc] init];
+ TestObject* rootObject = [TestObject testObject];
+ context[@"root"] = rootObject;
+ [context.virtualMachine addManagedReference:myPrivateProperties withOwner:rootObject];
+ [myPrivateProperties setValue:[JSValue valueWithBool:true inContext:context] forKey:@"is_ham"];
+ [myPrivateProperties setValue:[JSValue valueWithObject:@"hello!" inContext:context] forKey:@"message"];
+ [myPrivateProperties setValue:[JSValue valueWithInt32:42 inContext:context] forKey:@"my_number"];
+ [myPrivateProperties setValue:[JSValue valueWithNullInContext:context] forKey:@"definitely_null"];
+ [myPrivateProperties setValue:[JSValue valueWithUndefinedInContext:context] forKey:@"not_sure_if_undefined"];
+
+ JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
+
+ JSValue *isHam = [myPrivateProperties valueForKey:@"is_ham"];
+ JSValue *message = [myPrivateProperties valueForKey:@"message"];
+ JSValue *myNumber = [myPrivateProperties valueForKey:@"my_number"];
+ JSValue *definitelyNull = [myPrivateProperties valueForKey:@"definitely_null"];
+ JSValue *notSureIfUndefined = [myPrivateProperties valueForKey:@"not_sure_if_undefined"];
+ checkResult(@"is_ham is true", [isHam isBoolean] && [isHam toBool]);
+ checkResult(@"message is hello!", [message isString] && [@"hello!" isEqualToString:[message toString]]);
+ checkResult(@"my_number is 42", [myNumber isNumber] && [myNumber toInt32] == 42);
+ checkResult(@"definitely_null is null", [definitelyNull isNull]);
+ checkResult(@"not_sure_if_undefined is undefined", [notSureIfUndefined isUndefined]);
+ }
+
+ checkResult(@"is_ham is nil", ![myPrivateProperties valueForKey:@"is_ham"]);
+ checkResult(@"message is nil", ![myPrivateProperties valueForKey:@"message"]);
+ checkResult(@"my_number is 42", ![myPrivateProperties valueForKey:@"my_number"]);
+ checkResult(@"definitely_null is null", ![myPrivateProperties valueForKey:@"definitely_null"]);
+ checkResult(@"not_sure_if_undefined is undefined", ![myPrivateProperties valueForKey:@"not_sure_if_undefined"]);
+ }
+
@autoreleasepool {
JSContext *context = [[JSContext alloc] init];
__block int result;
JSContext *context = [[JSContext alloc] init];
context[@"TestObject"] = [TestObject class];
JSValue *result = [context evaluateScript:@"String(TestObject)"];
- checkResult(@"String(TestObject)", [result isEqualToObject:@"[object TestObjectConstructor]"]);
+ checkResult(@"String(TestObject)", [result isEqualToObject:@"function TestObject() {\n [native code]\n}"]);
}
@autoreleasepool {
[context.virtualMachine addManagedReference:managedTestObject withOwner:testObject];
}
}
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"UnexportedObject"] = [UnexportedObject class];
+ context[@"makeObject"] = ^{
+ return [[UnexportedObject alloc] init];
+ };
+ JSValue *result = [context evaluateScript:@"(makeObject() instanceof UnexportedObject)"];
+ checkResult(@"makeObject() instanceof UnexportedObject", [result isBoolean] && [result toBool]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"MyClass"] = ^{
+ JSValue *newThis = [JSValue valueWithNewObjectInContext:[JSContext currentContext]];
+ JSGlobalContextRef contextRef = [[JSContext currentContext] JSGlobalContextRef];
+ JSObjectRef newThisRef = JSValueToObject(contextRef, [newThis JSValueRef], NULL);
+ JSObjectSetPrototype(contextRef, newThisRef, [[JSContext currentContext][@"MyClass"][@"prototype"] JSValueRef]);
+ return newThis;
+ };
+
+ context[@"MyOtherClass"] = ^{
+ JSValue *newThis = [JSValue valueWithNewObjectInContext:[JSContext currentContext]];
+ JSGlobalContextRef contextRef = [[JSContext currentContext] JSGlobalContextRef];
+ JSObjectRef newThisRef = JSValueToObject(contextRef, [newThis JSValueRef], NULL);
+ JSObjectSetPrototype(contextRef, newThisRef, [[JSContext currentContext][@"MyOtherClass"][@"prototype"] JSValueRef]);
+ return newThis;
+ };
+
+ context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
+ NSLog(@"EXCEPTION: %@", [exception toString]);
+ context.exception = nil;
+ };
+
+ JSValue *constructor1 = context[@"MyClass"];
+ JSValue *constructor2 = context[@"MyOtherClass"];
+
+ JSValue *value1 = [context evaluateScript:@"new MyClass()"];
+ checkResult(@"value1 instanceof MyClass", [value1 isInstanceOf:constructor1]);
+ checkResult(@"!(value1 instanceof MyOtherClass)", ![value1 isInstanceOf:constructor2]);
+ checkResult(@"MyClass.prototype.constructor === MyClass", [[context evaluateScript:@"MyClass.prototype.constructor === MyClass"] toBool]);
+ checkResult(@"MyClass instanceof Function", [[context evaluateScript:@"MyClass instanceof Function"] toBool]);
+
+ JSValue *value2 = [context evaluateScript:@"new MyOtherClass()"];
+ checkResult(@"value2 instanceof MyOtherClass", [value2 isInstanceOf:constructor2]);
+ checkResult(@"!(value2 instanceof MyClass)", ![value2 isInstanceOf:constructor1]);
+ checkResult(@"MyOtherClass.prototype.constructor === MyOtherClass", [[context evaluateScript:@"MyOtherClass.prototype.constructor === MyOtherClass"] toBool]);
+ checkResult(@"MyOtherClass instanceof Function", [[context evaluateScript:@"MyOtherClass instanceof Function"] toBool]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"MyClass"] = ^{
+ NSLog(@"I'm intentionally not returning anything.");
+ };
+ JSValue *result = [context evaluateScript:@"new MyClass()"];
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"TestObject"] = [TestObject class];
+ JSValue *testObject = [context evaluateScript:@"(new TestObject())"];
+ checkResult(@"testObject instanceof TestObject", [testObject isInstanceOf:context[@"TestObject"]]);
+
+ context[@"TextXYZ"] = [TextXYZ class];
+ JSValue *textObject = [context evaluateScript:@"(new TextXYZ(\"Called TextXYZ constructor!\"))"];
+ checkResult(@"textObject instanceof TextXYZ", [textObject isInstanceOf:context[@"TextXYZ"]]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"ClassA"] = [ClassA class];
+ context[@"ClassB"] = [ClassB class];
+ context[@"ClassC"] = [ClassC class]; // Should print error message about too many inits found.
+
+ JSValue *a = [context evaluateScript:@"(new ClassA(42))"];
+ checkResult(@"a instanceof ClassA", [a isInstanceOf:context[@"ClassA"]]);
+
+ JSValue *b = [context evaluateScript:@"(new ClassB(42, 53))"];
+ checkResult(@"b instanceof ClassB", [b isInstanceOf:context[@"ClassB"]]);
+
+ JSValue *canConstructClassC = [context evaluateScript:@"(function() { \
+ try { \
+ (new ClassC(1, 2)); \
+ return true; \
+ } catch(e) { \
+ return false; \
+ } \
+ })()"];
+ checkResult(@"shouldn't be able to construct ClassC", ![canConstructClassC toBool]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"ClassD"] = [ClassD class];
+ context[@"ClassE"] = [ClassE class];
+
+ JSValue *d = [context evaluateScript:@"(new ClassD())"];
+ checkResult(@"Returning instance of ClassE from ClassD's init has correct class", [d isInstanceOf:context[@"ClassE"]]);
+ }
+
+ checkResult(@"result === undefined", [result isUndefined]);
+ checkResult(@"exception.message is correct'", context.exception
+ && [@"Objective-C blocks called as constructors must return an object." isEqualToString:[context.exception[@"message"] toString]]);
+ }
}
#else
MAJOR_VERSION = 537;
MINOR_VERSION = 51;
-TINY_VERSION = 1;
+TINY_VERSION = 2;
FULL_VERSION = $(MAJOR_VERSION).$(MINOR_VERSION).$(TINY_VERSION);
// The bundle version and short version string are set based on the current build configuration, see below.
DerivedSources/JavaScriptCore/LLIntAssembly.h
javascriptcore_sources += \
+ Source/JavaScriptCore/API/APICallbackFunction.h \
Source/JavaScriptCore/API/APICast.h \
Source/JavaScriptCore/API/APIShims.h \
Source/JavaScriptCore/API/JSAPIWrapperObject.h \
<ClInclude Include="..\heap\MarkStack.h" />\r
<ClInclude Include="..\heap\MarkStackInlines.h" />\r
<ClInclude Include="..\heap\PassWeak.h" />\r
+ <ClInclude Include="..\API\APICallbackFunction.h" />
<ClInclude Include="..\heap\Region.h" />\r
<ClInclude Include="..\heap\SlotVisitor.h" />\r
<ClInclude Include="..\heap\SlotVisitorInlines.h" />\r
<ClInclude Include="..\API\JavaScript.h">\r
<Filter>API</Filter>\r
</ClInclude>\r
+ <ClInclude Include="..\API\APICallbackFunction.h">
+ <Filter>API</Filter>
+ </ClInclude>
<ClInclude Include="..\API\JavaScriptCore.h">\r
<Filter>API</Filter>\r
</ClInclude>\r
1ACF7377171CA6FB00C9BB1E /* Weak.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1ACF7376171CA6FB00C9BB1E /* Weak.cpp */; };
2600B5A6152BAAA70091EE5F /* JSStringJoiner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2600B5A4152BAAA70091EE5F /* JSStringJoiner.cpp */; };
2600B5A7152BAAA70091EE5F /* JSStringJoiner.h in Headers */ = {isa = PBXBuildFile; fileRef = 2600B5A5152BAAA70091EE5F /* JSStringJoiner.h */; };
+ 2A3D60CF188DD95F00C9C528 /* APICallbackFunction.h in Headers */ = {isa = PBXBuildFile; fileRef = 2A3D60CE188DD95F00C9C528 /* APICallbackFunction.h */; };
41359CF30FDD89AD00206180 /* DateConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = D21202290AD4310C00ED79B6 /* DateConversion.h */; };
4443AE3316E188D90076F110 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51F0EB6105C86C6B00E6DF1B /* Foundation.framework */; };
451539B912DC994500EF7AC4 /* Yarr.h in Headers */ = {isa = PBXBuildFile; fileRef = 451539B812DC994500EF7AC4 /* Yarr.h */; settings = {ATTRIBUTES = (Private, ); }; };
1CAA8B4B0D32C39A0041BCFF /* JavaScriptCore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JavaScriptCore.h; sourceTree = "<group>"; };
2600B5A4152BAAA70091EE5F /* JSStringJoiner.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSStringJoiner.cpp; sourceTree = "<group>"; };
2600B5A5152BAAA70091EE5F /* JSStringJoiner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSStringJoiner.h; sourceTree = "<group>"; };
+ 2A3D60CE188DD95F00C9C528 /* APICallbackFunction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = APICallbackFunction.h; sourceTree = "<group>"; };
449097EE0F8F81B50076A327 /* FeatureDefines.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = FeatureDefines.xcconfig; sourceTree = "<group>"; };
451539B812DC994500EF7AC4 /* Yarr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Yarr.h; path = yarr/Yarr.h; sourceTree = "<group>"; };
45E12D8806A49B0F00E9DF84 /* jsc.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 4; lastKnownFileType = sourcecode.cpp.cpp; path = jsc.cpp; sourceTree = "<group>"; tabWidth = 4; };
1432EBD70A34CAD400717B9F /* API */ = {
isa = PBXGroup;
children = (
+ 2A3D60CE188DD95F00C9C528 /* APICallbackFunction.h */,
1482B78A0A4305AB00517CFC /* APICast.h */,
865F408710E7D56300947361 /* APIShims.h */,
1CAA8B4A0D32C39A0041BCFF /* JavaScript.h */,
65C0285D1717966800351E35 /* ARMv7DOpcode.h in Headers */,
BC18C4260E16F5CD00B34460 /* JSRetainPtr.h in Headers */,
14874AE615EBDE4A002E3587 /* JSScope.h in Headers */,
+ 2A3D60CF188DD95F00C9C528 /* APICallbackFunction.h in Headers */,
A7C0C4AC168103020017011D /* JSScriptRefPrivate.h in Headers */,
0F919D11157F332C004A4E7D /* JSSegmentedVariableObject.h in Headers */,
BC18C45E0E16F5CD00B34460 /* JSStack.h in Headers */,
{
moveToCachedReg(TrustedImmPtr(address), m_cachedMemoryTempRegister);
m_assembler.ldrb(dest, memoryTempRegister, ARM64Registers::zr);
+ if (dest == memoryTempRegister)
+ m_cachedMemoryTempRegister.invalidate();
}
void load8Signed(BaseIndex address, RegisterID dest)
Jump branch32(RelationalCondition cond, AbsoluteAddress left, RegisterID right)
{
- load32(left.m_ptr, getCachedMemoryTempRegisterIDAndInvalidate());
- return branch32(cond, memoryTempRegister, right);
+ load32(left.m_ptr, getCachedDataTempRegisterIDAndInvalidate());
+ return branch32(cond, dataTempRegister, right);
}
Jump branch32(RelationalCondition cond, AbsoluteAddress left, TrustedImm32 right)
Jump branch64(RelationalCondition cond, AbsoluteAddress left, RegisterID right)
{
- load64(left.m_ptr, getCachedMemoryTempRegisterIDAndInvalidate());
- return branch64(cond, memoryTempRegister, right);
+ load64(left.m_ptr, getCachedDataTempRegisterIDAndInvalidate());
+ return branch64(cond, dataTempRegister, right);
}
Jump branch64(RelationalCondition cond, Address left, RegisterID right)
intptr_t addressAsInt = reinterpret_cast<intptr_t>(address);
intptr_t addressDelta = addressAsInt - currentRegisterContents;
+ if (dest == memoryTempRegister)
+ m_cachedMemoryTempRegister.invalidate();
+
if (isInIntRange(addressDelta)) {
if (ARM64Assembler::canEncodeSImmOffset(addressDelta)) {
m_assembler.ldur<datasize>(dest, memoryTempRegister, addressDelta);
}
move(TrustedImmPtr(address), memoryTempRegister);
- m_cachedMemoryTempRegister.setValue(reinterpret_cast<intptr_t>(address));
+ if (dest == memoryTempRegister)
+ m_cachedMemoryTempRegister.invalidate();
+ else
+ m_cachedMemoryTempRegister.setValue(reinterpret_cast<intptr_t>(address));
m_assembler.ldr<datasize>(dest, memoryTempRegister, ARM64Registers::zr);
}
// Again, sadly, we don't propagate the fact that we've done InstanceOf
forNode(node).set(SpecBoolean);
break;
-
+
case Phi:
case Flush:
case PhantomLocal:
forNode(node).makeTop();
break;
+ case Unreachable:
+ RELEASE_ASSERT_NOT_REACHED();
+ break;
+
case ForceOSRExit:
node->setCanExit(true);
m_isValid = false;
}
case Return:
- case Throw:
- case ThrowReferenceError:
+ case Unreachable:
ASSERT(basicBlock->cfaBranchDirection == InvalidBranchDirection);
return false;
bool changed = false;
// Record which arguments are known to escape no matter what.
- for (unsigned i = codeBlock()->inlineCallFrames().size(); i--;) {
- InlineCallFrame* inlineCallFrame = &codeBlock()->inlineCallFrames()[i];
- if (m_graph.m_executablesWhoseArgumentsEscaped.contains(
- m_graph.executableFor(inlineCallFrame)))
- m_createsArguments.add(inlineCallFrame);
- }
+ for (unsigned i = codeBlock()->inlineCallFrames().size(); i--;)
+ pruneObviousArgumentCreations(&codeBlock()->inlineCallFrames()[i]);
+ pruneObviousArgumentCreations(0); // the machine call frame.
// Create data for variable access datas that we will want to analyze.
for (unsigned i = m_graph.m_variableAccessData.size(); i--;) {
NullableHashTraits<VariableAccessData*> > m_argumentsAliasing;
HashSet<VariableAccessData*> m_isLive;
+ void pruneObviousArgumentCreations(InlineCallFrame* inlineCallFrame)
+ {
+ ScriptExecutable* executable = jsCast<ScriptExecutable*>(m_graph.executableFor(inlineCallFrame));
+ if (m_graph.m_executablesWhoseArgumentsEscaped.contains(executable)
+ || executable->isStrictMode())
+ m_createsArguments.add(inlineCallFrame);
+ }
+
void observeBadArgumentsUse(Node* node)
{
if (!node)
LAST_OPCODE(op_end);
case op_throw:
- flushAllArgumentsAndCapturedVariablesInInlineStack();
addToGraph(Throw, get(currentInstruction[1].u.operand));
+ flushAllArgumentsAndCapturedVariablesInInlineStack();
+ addToGraph(Unreachable);
LAST_OPCODE(op_throw);
case op_throw_static_error:
- flushAllArgumentsAndCapturedVariablesInInlineStack();
addToGraph(ThrowReferenceError);
+ flushAllArgumentsAndCapturedVariablesInInlineStack();
+ addToGraph(Unreachable);
LAST_OPCODE(op_throw_static_error);
case op_call:
break;
}
case PutScopedVar: {
- if (node->child2() == registers && node->varNumber() == varNumber)
+ if (node->varNumber() != varNumber)
+ break;
+ if (node->child2() == registers)
return node->child3().node();
- break;
+ return 0;
}
case SetLocal: {
VariableAccessData* variableAccessData = node->variableAccessData();
Node* node = m_currentBlock->at(i);
switch (node->op()) {
case PutScopedVar: {
- if (node->child1() == scope && node->child2() == registers && node->varNumber() == varNumber)
+ if (node->varNumber() != varNumber)
+ break;
+ if (node->child1() == scope && node->child2() == registers)
return node;
- break;
+ return 0;
}
case GetScopedVar: {
Node* node = block->at(indexInBlock);
switch (node->op()) {
case Return:
- case Throw:
- case ThrowReferenceError:
+ case Unreachable:
case ForceOSRExit:
// Do nothing. These nodes will already do the right thing.
break;
case ValueToInt32: {
if (node->child1()->shouldSpeculateInteger()) {
setUseKindAndUnboxIfProfitable<Int32Use>(node->child1());
+ node->setOpAndDefaultFlags(Identity);
break;
}
case CountExecution:
case ForceOSRExit:
case CheckWatchdogTimer:
+ case Unreachable:
break;
#else
default:
case Jump:
case Branch:
case Return:
- case Throw:
- case ThrowReferenceError:
+ case Unreachable:
return true;
default:
return false;
macro(NewFunction, NodeResultJS) \
macro(NewFunctionExpression, NodeResultJS) \
\
+ /* These aren't terminals but always exit */ \
+ macro(Throw, NodeMustGenerate) \
+ macro(ThrowReferenceError, NodeMustGenerate) \
+ \
/* Block terminals. */\
macro(Jump, NodeMustGenerate) \
macro(Branch, NodeMustGenerate) \
macro(Return, NodeMustGenerate) \
- macro(Throw, NodeMustGenerate) \
- macro(ThrowReferenceError, NodeMustGenerate) \
+ macro(Unreachable, NodeMustGenerate) \
\
macro(GarbageValue, NodeResultJS | NodeClobbersWorld) \
\
case PutGlobalVar:
case PutGlobalVarCheck:
case CheckWatchdogTimer:
+ case Unreachable:
break;
// These gets ignored because it doesn't do anything.
switch (info.registerFormat()) {
case DataFormatNone: {
+ if (info.spillFormat() == DataFormatInteger || info.spillFormat() == DataFormatDouble) {
+ terminateSpeculativeExecution(Uncountable, JSValueRegs(), 0);
+ return allocate();
+ }
if (edge->hasConstant()) {
JSValue jsValue = valueOfJSConstant(edge.node());
JITCompiler::Jump isNotCell = m_jit.branch32(JITCompiler::NotEqual, tagGPR, JITCompiler::TrustedImm32(JSValue::CellTag));
if (node->child1().useKind() != UntypedUse)
- speculationCheck(BadType, JSValueRegs(tagGPR, payloadGPR), node->child1(), isNotCell);
+ DFG_TYPE_CHECK(JSValueRegs(tagGPR, payloadGPR), node->child1(), SpecCell, isNotCell);
if (!node->child1()->shouldSpeculateObject() || node->child1().useKind() == StringUse) {
m_jit.loadPtr(JITCompiler::Address(payloadGPR, JSCell::structureOffset()), tempGPR);
noResult(node);
break;
+ case Unreachable:
+ RELEASE_ASSERT_NOT_REACHED();
+ break;
+
case Nop:
case LastNodeType:
RELEASE_ASSERT_NOT_REACHED();
m_jit.move64ToDouble(gpr, fpr);
unlock(gpr);
- m_fprs.retain(fpr, virtualRegister, SpillOrderDouble);
- info.fillDouble(*m_stream, fpr);
return fpr;
}
if (isNumberConstant(edge.node())) {
// This is a no-op.
noResult(node);
break;
+
+ case Unreachable:
+ RELEASE_ASSERT_NOT_REACHED();
+ break;
case Nop:
RELEASE_ASSERT_NOT_REACHED();
inline void SlotVisitor::copyLater(JSCell* owner, void* ptr, size_t bytes)
{
+ ASSERT(bytes);
CopiedBlock* block = CopiedSpace::blockFor(ptr);
if (block->isOversize()) {
m_shared.m_copiedSpace->pin(block);
if (Debugger* debugger = callFrame->dynamicGlobalObject()->debugger()) {
DebuggerCallFrame debuggerCallFrame(callFrame, exceptionValue);
- bool hasHandler = codeBlock->handlerForBytecodeOffset(bytecodeOffset);
+ bool hasHandler = false;
+ if (!isTermination) {
+ VM* vm = &callFrame->vm();
+ CallFrame* currentFrame = callFrame;
+ CodeBlock* currentCB = codeBlock;
+ unsigned currentOffset = bytecodeOffset;
+ while (currentFrame) {
+ if (currentCB && currentCB->handlerForBytecodeOffset(currentOffset)) {
+ hasHandler = true;
+ break;
+ }
+ currentFrame = getCallerInfo(vm, currentFrame, currentOffset, currentCB);
+ }
+ }
debugger->exception(debuggerCallFrame, codeBlock->ownerExecutable()->sourceID(), codeBlock->lineNumberForBytecodeOffset(bytecodeOffset), 0, hasHandler);
}
}
inline LogicalOpNode::LogicalOpNode(const JSTokenLocation& location, ExpressionNode* expr1, ExpressionNode* expr2, LogicalOperator oper)
- : ExpressionNode(location, ResultType::booleanType())
+ : ExpressionNode(location, ResultType::forLogicalOp(expr1->resultDescriptor(), expr2->resultDescriptor()))
, m_expr1(expr1)
, m_expr2(expr2)
, m_operator(oper)
return stringType();
return stringOrNumberType();
}
-
+
+ // Unlike in C, a logical op produces the value of the
+ // last expression evaluated (and not true or false).
+ static ResultType forLogicalOp(ResultType op1, ResultType op2)
+ {
+ if (op1.definitelyIsBoolean() && op2.definitelyIsBoolean())
+ return booleanType();
+ if (op1.definitelyIsNumber() && op2.definitelyIsNumber())
+ return numberType();
+ if (op1.definitelyIsString() && op2.definitelyIsString())
+ return stringType();
+ return unknownType();
+ }
+
static ResultType forBitOp()
{
return numberTypeIsInt32();
// is added.
#define FIRST_VECTOR_GROW 4U
+#define MIN_BEYOND_LENGTH_SPARSE_INDEX 1000
+
// Our policy for when to use a vector and when to use a sparse map.
// For all array indices under MIN_SPARSE_ARRAY_INDEX, we always use a vector.
// When indices greater than MIN_SPARSE_ARRAY_INDEX are involved, we use a vector
return length / minDensityMultiplier <= numValues;
}
+inline bool indexIsSufficientlyBeyondLengthForSparseMap(unsigned i, unsigned length)
+{
+ return i >= MIN_BEYOND_LENGTH_SPARSE_INDEX && i > length;
+}
+
inline IndexingHeader indexingHeaderForArray(unsigned length, unsigned vectorLength)
{
IndexingHeader result;
// so only if it's not horribly slow.
if (oldLength - (startIndex + count) >= MIN_SPARSE_ARRAY_INDEX)
return shiftCountWithArrayStorage(startIndex, count, ensureArrayStorage(exec->vm()));
-
+
+ // Storing to a hole is fine since we're still having a good time. But reading from a hole
+ // is totally not fine, since we might have to read from the proto chain.
+ // We have to check for holes before we start moving things around so that we don't get halfway
+ // through shifting and then realize we should have been in ArrayStorage mode.
unsigned end = oldLength - count;
for (unsigned i = startIndex; i < end; ++i) {
- // Storing to a hole is fine since we're still having a good time. But reading
- // from a hole is totally not fine, since we might have to read from the proto
- // chain.
JSValue v = m_butterfly->contiguous()[i + count].get();
- if (UNLIKELY(!v)) {
- // The purpose of this path is to ensure that we don't make the same
- // mistake in the future: shiftCountWithArrayStorage() can't do anything
- // about holes (at least for now), but it can detect them quickly. So
- // we convert to array storage and then allow the array storage path to
- // figure it out.
+ if (UNLIKELY(!v))
return shiftCountWithArrayStorage(startIndex, count, ensureArrayStorage(exec->vm()));
- }
+ }
+
+ for (unsigned i = startIndex; i < end; ++i) {
+ JSValue v = m_butterfly->contiguous()[i + count].get();
+ ASSERT(v);
// No need for a barrier since we're just moving data around in the same vector.
// This is in line with our standing assumption that we won't have a deletion
// barrier.
// so only if it's not horribly slow.
if (oldLength - (startIndex + count) >= MIN_SPARSE_ARRAY_INDEX)
return shiftCountWithArrayStorage(startIndex, count, ensureArrayStorage(exec->vm()));
-
+
+ // Storing to a hole is fine since we're still having a good time. But reading from a hole
+ // is totally not fine, since we might have to read from the proto chain.
+ // We have to check for holes before we start moving things around so that we don't get halfway
+ // through shifting and then realize we should have been in ArrayStorage mode.
unsigned end = oldLength - count;
for (unsigned i = startIndex; i < end; ++i) {
- // Storing to a hole is fine since we're still having a good time. But reading
- // from a hole is totally not fine, since we might have to read from the proto
- // chain.
double v = m_butterfly->contiguousDouble()[i + count];
- if (UNLIKELY(v != v)) {
- // The purpose of this path is to ensure that we don't make the same
- // mistake in the future: shiftCountWithArrayStorage() can't do anything
- // about holes (at least for now), but it can detect them quickly. So
- // we convert to array storage and then allow the array storage path to
- // figure it out.
+ if (UNLIKELY(v != v))
return shiftCountWithArrayStorage(startIndex, count, ensureArrayStorage(exec->vm()));
- }
+ }
+
+ for (unsigned i = startIndex; i < end; ++i) {
+ double v = m_butterfly->contiguousDouble()[i + count];
+ ASSERT(v == v);
// No need for a barrier since we're just moving data around in the same vector.
// This is in line with our standing assumption that we won't have a deletion
// barrier.
return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(exec->vm()));
ensureLength(exec->vm(), oldLength + count);
-
+
+ // We have to check for holes before we start moving things around so that we don't get halfway
+ // through shifting and then realize we should have been in ArrayStorage mode.
for (unsigned i = oldLength; i-- > startIndex;) {
JSValue v = m_butterfly->contiguous()[i].get();
if (UNLIKELY(!v))
return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(exec->vm()));
+ }
+
+ for (unsigned i = oldLength; i-- > startIndex;) {
+ JSValue v = m_butterfly->contiguous()[i].get();
+ ASSERT(v);
m_butterfly->contiguous()[i + count].setWithoutWriteBarrier(v);
}
ensureLength(exec->vm(), oldLength + count);
+ // We have to check for holes before we start moving things around so that we don't get halfway
+ // through shifting and then realize we should have been in ArrayStorage mode.
for (unsigned i = oldLength; i-- > startIndex;) {
double v = m_butterfly->contiguousDouble()[i];
if (UNLIKELY(v != v))
return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(exec->vm()));
+ }
+
+ for (unsigned i = oldLength; i-- > startIndex;) {
+ double v = m_butterfly->contiguousDouble()[i];
+ ASSERT(v == v);
m_butterfly->contiguousDouble()[i + count] = v;
}
if (mayInterceptIndexedAccesses())
return;
- setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AddIndexedAccessors));
+ setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AddIndexedAccessors), m_butterfly);
if (!vm.prototypeMap.isPrototype(this))
return;
ContiguousJSValues JSObject::convertUndecidedToInt32(VM& vm)
{
ASSERT(hasUndecided(structure()->indexingType()));
- setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateInt32));
+ setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateInt32), m_butterfly);
return m_butterfly->contiguousInt32();
}
for (unsigned i = m_butterfly->vectorLength(); i--;)
m_butterfly->contiguousDouble()[i] = QNaN;
- setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateDouble));
+ setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateDouble), m_butterfly);
return m_butterfly->contiguousDouble();
}
ContiguousJSValues JSObject::convertUndecidedToContiguous(VM& vm)
{
ASSERT(hasUndecided(structure()->indexingType()));
- setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateContiguous));
+ setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateContiguous), m_butterfly);
return m_butterfly->contiguous();
}
*currentAsDouble = v.asInt32();
}
- setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateDouble));
+ setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateDouble), m_butterfly);
return m_butterfly->contiguousDouble();
}
{
ASSERT(hasInt32(structure()->indexingType()));
- setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateContiguous));
+ setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateContiguous), m_butterfly);
return m_butterfly->contiguous();
}
currentAsValue->setWithoutWriteBarrier(v);
}
- setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateContiguous));
+ setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateContiguous), m_butterfly);
return m_butterfly->contiguous();
}
case NonArrayWithArrayStorage:
case ArrayWithArrayStorage: {
Structure* newStructure = Structure::nonPropertyTransition(vm, structure(), SwitchToSlowPutArrayStorage);
- setStructure(vm, newStructure);
+ setStructure(vm, newStructure, m_butterfly);
break;
}
vm.prototypeMap.addPrototype(asObject(prototype));
Structure* newStructure = Structure::changePrototypeTransition(vm, structure(), prototype);
- setStructure(vm, newStructure);
+ setStructure(vm, newStructure, m_butterfly);
if (!newStructure->anyObjectInChainMayInterceptIndexedAccesses())
return;
// getters and setters, though, we also need to change our Structure
// if we override an existing non-getter or non-setter.
if (slot.type() != PutPropertySlot::NewProperty)
- setStructure(vm, Structure::attributeChangeTransition(vm, structure(), propertyName, attributes));
+ setStructure(vm, Structure::attributeChangeTransition(vm, structure(), propertyName, attributes), m_butterfly);
if (attributes & ReadOnly)
structure()->setContainsReadOnlyProperties();
if (isSealed(vm))
return;
preventExtensions(vm);
- setStructure(vm, Structure::sealTransition(vm, structure()));
+ setStructure(vm, Structure::sealTransition(vm, structure()), m_butterfly);
}
void JSObject::freeze(VM& vm)
if (isFrozen(vm))
return;
preventExtensions(vm);
- setStructure(vm, Structure::freezeTransition(vm, structure()));
+ setStructure(vm, Structure::freezeTransition(vm, structure()), m_butterfly);
}
void JSObject::preventExtensions(VM& vm)
{
enterDictionaryIndexingMode(vm);
if (isExtensible())
- setStructure(vm, Structure::preventExtensionsTransition(vm, structure()));
+ setStructure(vm, Structure::preventExtensionsTransition(vm, structure()), m_butterfly);
}
// This presently will flatten to an uncachable dictionary; this is suitable
}
if (!structure()->isUncacheableDictionary())
- setStructure(vm, Structure::toUncacheableDictionaryTransition(vm, structure()));
+ setStructure(vm, Structure::toUncacheableDictionaryTransition(vm, structure()), m_butterfly);
for (const ClassInfo* info = classInfo(); info; info = info->parentClass) {
const HashTable* hashTable = info->propHashTable(globalObject()->globalExec());
return true;
}
- setStructure(vm, Structure::removePropertyTransition(vm, structure(), propertyName, offset));
+ setStructure(vm, Structure::removePropertyTransition(vm, structure(), propertyName, offset), m_butterfly);
if (offset == invalidOffset)
return false;
putDirectUndefined(offset);
if (i >= MAX_ARRAY_INDEX - 1
|| (i >= MIN_SPARSE_ARRAY_INDEX
- && !isDenseEnoughForVector(i, countElements<indexingShape>(m_butterfly)))) {
+ && !isDenseEnoughForVector(i, countElements<indexingShape>(m_butterfly)))
+ || indexIsSufficientlyBeyondLengthForSparseMap(i, m_butterfly->vectorLength())) {
ASSERT(i <= MAX_ARRAY_INDEX);
ensureArrayStorageSlow(vm);
SparseArrayValueMap* map = allocateSparseIndexMap(vm);
// First, handle cases where we don't currently have a sparse map.
if (LIKELY(!map)) {
- // If the array is not extensible, we should have entered dictionary mode, and created the spare map.
+ // If the array is not extensible, we should have entered dictionary mode, and created the sparse map.
ASSERT(isExtensible());
// Update m_length if necessary.
storage->setLength(i + 1);
// Check that it is sensible to still be using a vector, and then try to grow the vector.
- if (LIKELY((isDenseEnoughForVector(i, storage->m_numValuesInVector)) && increaseVectorLength(vm, i + 1))) {
+ if (LIKELY(!indexIsSufficientlyBeyondLengthForSparseMap(i, storage->vectorLength())
+ && isDenseEnoughForVector(i, storage->m_numValuesInVector)
+ && increaseVectorLength(vm, i + 1))) {
// success! - reread m_storage since it has likely been reallocated, and store to the vector.
storage = arrayStorage();
storage->m_vector[i].set(vm, this, value);
ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm));
break;
}
- if (i >= MIN_SPARSE_ARRAY_INDEX) {
+ if (indexIsSufficientlyBeyondLengthForSparseMap(i, 0) || i >= MIN_SPARSE_ARRAY_INDEX) {
putByIndexBeyondVectorLengthWithArrayStorage(
exec, i, value, shouldThrow, createArrayStorage(vm, 0, 0));
break;
if (LIKELY(
!attributes
&& (isDenseEnoughForVector(i, storage->m_numValuesInVector))
- && increaseVectorLength(vm, i + 1))) {
+ && !indexIsSufficientlyBeyondLengthForSparseMap(i, storage->vectorLength()))
+ && increaseVectorLength(vm, i + 1)) {
// success! - reread m_storage since it has likely been reallocated, and store to the vector.
storage = arrayStorage();
storage->m_vector[i].set(vm, this, value);
void setButterfly(VM&, Butterfly*, Structure*);
void setButterflyWithoutChangingStructure(Butterfly*); // You probably don't want to call this.
+ void setStructure(VM&, Structure*, Butterfly* = 0);
void setStructureAndReallocateStorageIfNecessary(VM&, unsigned oldCapacity, Structure*);
void setStructureAndReallocateStorageIfNecessary(VM&, Structure*);
{
ASSERT(structure);
ASSERT(!butterfly == (!structure->outOfLineCapacity() && !hasIndexingHeader(structure->indexingType())));
- setStructure(vm, structure);
+ setStructure(vm, structure, butterfly);
m_butterfly = butterfly;
}
return true;
}
// case (2) Despecify, fall through to (3).
- setStructure(vm, Structure::despecifyFunctionTransition(vm, structure(), propertyName));
+ setStructure(vm, Structure::despecifyFunctionTransition(vm, structure(), propertyName), m_butterfly);
}
// case (3) set the slot, do the put, return.
return true;
}
+inline void JSObject::setStructure(VM& vm, Structure* structure, Butterfly* butterfly)
+{
+ JSCell::setStructure(vm, structure);
+ ASSERT_UNUSED(butterfly, !butterfly == !(structure->outOfLineCapacity() || hasIndexingHeader(structure->indexingType())));
+}
+
inline void JSObject::setStructureAndReallocateStorageIfNecessary(VM& vm, unsigned oldCapacity, Structure* newStructure)
{
ASSERT(oldCapacity <= newStructure->outOfLineCapacity());
if (oldCapacity == newStructure->outOfLineCapacity()) {
- setStructure(vm, newStructure);
+ setStructure(vm, newStructure, m_butterfly);
return;
}
operations->append(ResolveOperation::checkForDynamicEntriesBeforeGlobalScope());
if (putToBaseOperation) {
+ unsigned currentAttributes;
+ JSCell* currentSpecificFunction;
+ PropertyOffset offset = globalObject->structure()->get(callFrame->vm(), identifier, currentAttributes, currentSpecificFunction);
+ ASSERT_UNUSED(offset, offset != invalidOffset);
+ ASSERT_UNUSED(offset, offset == slot.cachedOffset());
+ // We just assume that we are clobbering the global specialisation
+ if (currentSpecificFunction)
+ globalObject->setStructure(callFrame->vm(), Structure::despecifyFunctionTransition(callFrame->vm(), globalObject->structure(), identifier));
putToBaseOperation->m_isDynamic = requiresDynamicChecks;
putToBaseOperation->m_kind = PutToBaseOperation::GlobalPropertyPut;
putToBaseOperation->m_structure.set(callFrame->vm(), callFrame->codeBlock()->ownerExecutable(), globalObject->structure());
static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
{
- return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), &s_info, ArrayWithArrayStorage);
+ return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), &s_info, ArrayWithSlowPutArrayStorage);
}
static void visitChildren(JSCell*, SlotVisitor&);
}
m_dictionaryKind = NoneDictionaryKind;
+
+ // If the object had a Butterfly but after flattening/compacting we no longer have need of it,
+ // we need to zero it out because the collector depends on the Structure to know the size for copying.
+ if (object->butterfly() && !this->outOfLineCapacity() && !hasIndexingHeader(this->indexingType()))
+ object->setButterfly(vm, 0, this);
+
return this;
}