From 12899fa232562c774004a3a9d7d3149944dec712 Mon Sep 17 00:00:00 2001 From: Apple Date: Fri, 13 Feb 2015 03:14:50 +0000 Subject: [PATCH] JavaScriptCore-1218.33.tar.gz --- API/APICallbackFunction.h | 107 +++++++ API/JSCallbackConstructor.cpp | 36 +-- API/JSCallbackConstructor.h | 4 + API/JSCallbackFunction.cpp | 33 +- API/JSCallbackFunction.h | 13 +- API/JSManagedValue.mm | 163 +++++++++- API/JSValue.mm | 12 +- API/JSWrapperMap.mm | 100 +++++- API/ObjCCallbackFunction.h | 22 +- API/ObjCCallbackFunction.mm | 151 ++++++++- API/tests/minidom.c | 1 + API/tests/testapi.c | 1 + API/tests/testapi.mm | 297 +++++++++++++++++- Configurations/Version.xcconfig | 2 +- GNUmakefile.list.am | 1 + JavaScriptCore.vcxproj/JavaScriptCore.vcxproj | 1 + .../JavaScriptCore.vcxproj.filters | 3 + JavaScriptCore.xcodeproj/project.pbxproj | 4 + assembler/MacroAssemblerARM64.h | 18 +- dfg/DFGAbstractState.cpp | 9 +- dfg/DFGArgumentsSimplificationPhase.cpp | 17 +- dfg/DFGByteCodeParser.cpp | 6 +- dfg/DFGCSEPhase.cpp | 12 +- dfg/DFGConstantFoldingPhase.cpp | 3 +- dfg/DFGFixupPhase.cpp | 2 + dfg/DFGNode.h | 3 +- dfg/DFGNodeType.h | 7 +- dfg/DFGPredictionPropagationPhase.cpp | 1 + dfg/DFGSpeculativeJIT32_64.cpp | 10 +- dfg/DFGSpeculativeJIT64.cpp | 6 +- heap/SlotVisitorInlines.h | 1 + interpreter/Interpreter.cpp | 15 +- parser/NodeConstructors.h | 2 +- parser/ResultType.h | 15 +- runtime/ArrayConventions.h | 7 + runtime/JSArray.cpp | 60 ++-- runtime/JSObject.cpp | 44 +-- runtime/JSObject.h | 13 +- runtime/JSScope.cpp | 8 + runtime/RegExpMatchesArray.h | 2 +- runtime/Structure.cpp | 6 + 41 files changed, 1026 insertions(+), 192 deletions(-) create mode 100644 API/APICallbackFunction.h diff --git a/API/APICallbackFunction.h b/API/APICallbackFunction.h new file mode 100644 index 0000000..0298ecb --- /dev/null +++ b/API/APICallbackFunction.h @@ -0,0 +1,107 @@ +/* + * 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 + +namespace JSC { + +struct APICallbackFunction { + +template static EncodedJSValue JSC_HOST_CALL call(ExecState*); +template static EncodedJSValue JSC_HOST_CALL construct(ExecState*); + +}; + +template +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(exec->argumentCount()); + Vector 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(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 +EncodedJSValue JSC_HOST_CALL APICallbackFunction::construct(ExecState* exec) +{ + JSObject* constructor = exec->callee(); + JSContextRef ctx = toRef(exec); + JSObjectRef constructorRef = toRef(constructor); + + JSObjectCallAsConstructorCallback callback = jsCast(constructor)->constructCallback(); + if (callback) { + size_t argumentCount = exec->argumentCount(); + Vector 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(constructor)->classRef(), 0))); +} + +} // namespace JSC + +#endif // APICallbackFunction_h diff --git a/API/JSCallbackConstructor.cpp b/API/JSCallbackConstructor.cpp index 8340c10..2fdea5f 100644 --- a/API/JSCallbackConstructor.cpp +++ b/API/JSCallbackConstructor.cpp @@ -26,8 +26,9 @@ #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" @@ -65,40 +66,9 @@ void JSCallbackConstructor::destroy(JSCell* cell) static_cast(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(constructor)->callback(); - if (callback) { - size_t argumentCount = exec->argumentCount(); - Vector 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(constructor)->classRef(), 0))); -} - ConstructType JSCallbackConstructor::getConstructData(JSCell*, ConstructData& constructData) { - constructData.native.function = constructJSCallback; + constructData.native.function = APICallbackFunction::construct; return ConstructTypeHost; } diff --git a/API/JSCallbackConstructor.h b/API/JSCallbackConstructor.h index 72100e6..b2eaa16 100644 --- a/API/JSCallbackConstructor.h +++ b/API/JSCallbackConstructor.h @@ -59,8 +59,12 @@ protected: 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; }; diff --git a/API/JSCallbackFunction.cpp b/API/JSCallbackFunction.cpp index c29b907..38fdc1c 100644 --- a/API/JSCallbackFunction.cpp +++ b/API/JSCallbackFunction.cpp @@ -26,8 +26,9 @@ #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" @@ -63,37 +64,9 @@ JSCallbackFunction* JSCallbackFunction::create(ExecState* exec, JSGlobalObject* 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 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(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; return CallTypeHost; } diff --git a/API/JSCallbackFunction.h b/API/JSCallbackFunction.h index 885ef94..ea1d1ab 100644 --- a/API/JSCallbackFunction.h +++ b/API/JSCallbackFunction.h @@ -32,10 +32,7 @@ namespace JSC { class JSCallbackFunction : public InternalFunction { -protected: - JSCallbackFunction(JSGlobalObject*, Structure*, JSObjectCallAsFunctionCallback); - void finishCreation(VM&, const String& name); - + friend struct APICallbackFunction; public: typedef InternalFunction Base; @@ -50,11 +47,13 @@ public: 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; }; diff --git a/API/JSManagedValue.mm b/API/JSManagedValue.mm index f336ba6..0a01f22 100644 --- a/API/JSManagedValue.mm +++ b/API/JSManagedValue.mm @@ -50,8 +50,125 @@ static JSManagedValueHandleOwner* managedValueHandleOwner() 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 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 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 m_object; + JSC::Weak m_string; + } u; +}; + @implementation JSManagedValue { - JSC::Weak m_value; + JSC::Weak m_globalObject; + WeakValueRef m_weakValue; } + (JSManagedValue *)managedValueWithValue:(JSValue *)value @@ -70,30 +187,46 @@ static JSManagedValueHandleOwner* managedValueHandleOwner() if (!self) return nil; - if (!value || !JSValueIsObject([value.context JSGlobalContextRef], [value JSValueRef])) { - JSC::Weak weak; - m_value.swap(weak); - } else { - JSC::JSObject* object = toJS(const_cast([value JSValueRef])); - JSC::Weak 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 weak(globalObject, managedValueHandleOwner(), self); + m_globalObject.swap(weak); + + JSC::JSValue jsValue = toJS(exec, [value JSValueRef]); + if (jsValue.isObject()) + m_weakValue.setObject(JSC::jsCast(jsValue.asCell()), self); + else if (jsValue.isString()) + m_weakValue.setString(JSC::jsCast(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 diff --git a/API/JSValue.mm b/API/JSValue.mm index a380964..f007573 100644 --- a/API/JSValue.mm +++ b/API/JSValue.mm @@ -37,8 +37,10 @@ #import "ObjcRuntimeExtras.h" #import "Operations.h" #import "JSCJSValue.h" +#import "StrongInlines.h" #import #import +#import #import #import #import @@ -594,6 +596,7 @@ private: JSGlobalContextRef m_context; HashMap m_objectMap; Vector m_worklist; + Vector> m_jsValues; }; inline id JSContainerConvertor::convert(JSValueRef value) @@ -610,6 +613,8 @@ inline id JSContainerConvertor::convert(JSValueRef value) void JSContainerConvertor::add(Task task) { + JSC::ExecState* exec = toJS(m_context); + m_jsValues.append(JSC::Strong(exec->vm(), toJSForGC(exec, task.js))); m_objectMap.add(task.js, task.objc); if (task.type != ContainerNone) m_worklist.append(task); @@ -666,6 +671,7 @@ static JSContainerConvertor::Task valueToObjectWithoutCopy(JSGlobalContextRef co 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()); @@ -745,7 +751,7 @@ id valueToString(JSGlobalContextRef context, JSValueRef value, JSValueRef* excep return nil; } - NSString *stringNS = [(NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsstring) autorelease]; + NSString *stringNS = HardAutorelease(JSStringCopyCFString(kCFAllocatorDefault, jsstring)); JSStringRelease(jsstring); return stringNS; } @@ -816,6 +822,7 @@ private: JSContext *m_context; HashMap m_objectMap; Vector m_worklist; + Vector> m_jsValues; }; JSValueRef ObjcContainerConvertor::convert(id object) @@ -833,6 +840,8 @@ JSValueRef ObjcContainerConvertor::convert(id object) void ObjcContainerConvertor::add(ObjcContainerConvertor::Task task) { + JSC::ExecState* exec = toJS(m_context.JSGlobalContextRef); + m_jsValues.append(JSC::Strong(exec->vm(), toJSForGC(exec, task.js))); m_objectMap.add(task.objc, task.js); if (task.type != ContainerNone) m_worklist.append(task); @@ -912,6 +921,7 @@ JSValueRef objectToValue(JSContext *context, id object) if (task.type == ContainerNone) return task.js; + JSC::APIEntryShim entryShim(toJS(contextRef)); ObjcContainerConvertor convertor(context); convertor.add(task); ASSERT(!convertor.isWorkListEmpty()); diff --git a/API/JSWrapperMap.mm b/API/JSWrapperMap.mm index 4dde1a6..5ec64d9 100644 --- a/API/JSWrapperMap.mm +++ b/API/JSWrapperMap.mm @@ -40,6 +40,13 @@ #import "WeakGCMap.h" #import #import +#import + +#if OS(DARWIN) +#include + +static const int32_t webkitFirstVersionWithInitConstructorSupport = 0x2193302; // 537.51.2 +#endif @class JSObjCClassInfo; @@ -93,6 +100,16 @@ done: 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); @@ -121,6 +138,18 @@ static JSValue *objectWithCustomBrand(JSContext *context, NSString *brand, Class 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) @@ -163,6 +192,11 @@ static void copyMethodsToObject(JSContext *context, Class objcClass, Protocol *p 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) @@ -329,6 +363,59 @@ static void copyPrototypeProperties(JSContext *context, Class objcClass, Protoco [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 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); @@ -357,7 +444,7 @@ static void copyPrototypeProperties(JSContext *context, Class objcClass, Protoco 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)); @@ -387,8 +474,13 @@ static void copyPrototypeProperties(JSContext *context, Class objcClass, Protoco 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) @@ -501,7 +593,7 @@ id tryUnwrapObjcObject(JSGlobalContextRef context, JSValueRef value) ASSERT(!exception); if (toJS(object)->inherits(&JSC::JSCallbackObject::s_info)) return (id)JSC::jsCast(toJS(object))->wrappedObject(); - if (id target = tryUnwrapBlock(object)) + if (id target = tryUnwrapConstructor(object)) return target; return nil; } diff --git a/API/ObjCCallbackFunction.h b/API/ObjCCallbackFunction.h index 0218cd8..0b0b543 100644 --- a/API/ObjCCallbackFunction.h +++ b/API/ObjCCallbackFunction.h @@ -34,19 +34,21 @@ #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); + static ObjCCallbackFunction* create(VM&, JSGlobalObject*, const String& name, PassOwnPtr); static void destroy(JSCell*); static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) @@ -57,12 +59,20 @@ public: 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); + ObjCCallbackFunction(VM&, JSGlobalObject*, JSObjectCallAsFunctionCallback, JSObjectCallAsConstructorCallback, PassOwnPtr); 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 m_impl; }; diff --git a/API/ObjCCallbackFunction.mm b/API/ObjCCallbackFunction.mm index cc342f5..e0bd2d7 100644 --- a/API/ObjCCallbackFunction.mm +++ b/API/ObjCCallbackFunction.mm @@ -28,6 +28,7 @@ #if JSC_OBJC_API_ENABLED +#import "APICallbackFunction.h" #import "APICast.h" #import "APIShims.h" #import "Error.h" @@ -384,6 +385,7 @@ public: }; enum CallbackType { + CallbackInitMethod, CallbackInstanceMethod, CallbackClassMethod, CallbackBlock @@ -401,12 +403,14 @@ public: , 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]; } @@ -429,6 +433,25 @@ public: 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; @@ -466,18 +489,49 @@ static JSValueRef objCCallbackFunctionCallAsFunction(JSContextRef callerContext, 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(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 impl) - : Base(globalObject, globalObject->objcCallbackFunctionStructure(), callback) +ObjCCallbackFunction::ObjCCallbackFunction(JSC::VM&, JSC::JSGlobalObject* globalObject, JSObjectCallAsFunctionCallback functionCallback, JSObjectCallAsConstructorCallback constructCallback, PassOwnPtr 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 impl) +ObjCCallbackFunction* ObjCCallbackFunction::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, const String& name, PassOwnPtr impl) { - ObjCCallbackFunction* function = new (NotNull, allocateCell(*exec->heap())) ObjCCallbackFunction(globalObject, objCCallbackFunctionCallAsFunction, impl); - function->finishCreation(exec->vm(), name); + ObjCCallbackFunction* function = new (NotNull, allocateCell(vm.heap)) ObjCCallbackFunction(vm, globalObject, objCCallbackFunctionCallAsFunction, objCCallbackFunctionCallAsConstructor, impl); + function->finishCreation(vm, name); return function; } @@ -486,21 +540,59 @@ void ObjCCallbackFunction::destroy(JSCell* cell) static_cast(cell)->ObjCCallbackFunction::~ObjCCallbackFunction(); } + +CallType ObjCCallbackFunction::getCallData(JSCell*, CallData& callData) +{ + callData.native.function = APICallbackFunction::call; + return CallTypeHost; +} + +ConstructType ObjCCallbackFunction::getConstructData(JSCell* cell, ConstructData& constructData) +{ + ObjCCallbackFunction* callback = jsCast(cell); + if (!callback->impl()->isConstructible()) + return Base::getConstructData(cell, constructData); + constructData.native.function = APICallbackFunction::construct; + 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; @@ -519,7 +611,18 @@ JSValueRef ObjCCallbackFunctionImpl::call(JSContext *context, JSObjectRef thisOb [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 @@ -550,6 +653,7 @@ static JSObjectRef objCCallbackFunctionForInvocation(JSContext *context, NSInvoc return nil; switch (type) { + case CallbackInitMethod: case CallbackInstanceMethod: case CallbackClassMethod: // Methods are passed two implicit arguments - (id)self, and the selector. @@ -582,16 +686,24 @@ static JSObjectRef objCCallbackFunctionForInvocation(JSContext *context, NSInvoc JSC::ExecState* exec = toJS([context JSGlobalContextRef]); JSC::APIEntryShim shim(exec); OwnPtr 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)); } @@ -601,15 +713,24 @@ JSObjectRef objCCallbackFunctionForBlock(JSContext *context, id target) 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(toJS(object))->impl()->wrappedBlock(); + JSC::ObjCCallbackFunctionImpl* impl = static_cast(toJS(object))->impl(); + if (!impl->isConstructible()) + return nil; + return impl->wrappedConstructor(); } #endif diff --git a/API/tests/minidom.c b/API/tests/minidom.c index 8614e51..f4ccf91 100644 --- a/API/tests/minidom.c +++ b/API/tests/minidom.c @@ -105,6 +105,7 @@ static char* createStringWithContentsOfFile(const char* fileName) FILE* f = fopen(fileName, "r"); if (!f) { fprintf(stderr, "Could not open file: %s\n", fileName); + free(buffer); return 0; } diff --git a/API/tests/testapi.c b/API/tests/testapi.c index a53a340..83c605c 100644 --- a/API/tests/testapi.c +++ b/API/tests/testapi.c @@ -1980,6 +1980,7 @@ static char* createStringWithContentsOfFile(const char* fileName) FILE* f = fopen(fileName, "r"); if (!f) { fprintf(stderr, "Could not open file: %s\n", fileName); + free(buffer); return 0; } diff --git a/API/tests/testapi.mm b/API/tests/testapi.mm index 0ab82cd..d85d6e3 100644 --- a/API/tests/testapi.mm +++ b/API/tests/testapi.mm @@ -35,6 +35,12 @@ extern "C" void testObjectiveCAPI(void); #if JSC_OBJC_API_ENABLED +@interface UnexportedObject : NSObject +@end + +@implementation UnexportedObject +@end + @protocol ParentObject @end @@ -50,6 +56,7 @@ extern "C" void testObjectiveCAPI(void); @end @protocol TestObject +- (id)init; @property int variable; @property (readonly) int six; @property CGPoint point; @@ -101,6 +108,7 @@ JSExportAs(testArgumentTypes, bool testXYZTested = false; @protocol TextXYZ +- (id)initWithString:(NSString*)string; @property int x; @property (readonly) int y; @property (assign) JSValue *onclick; @@ -122,6 +130,16 @@ bool testXYZTested = false; @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; @@ -241,6 +259,145 @@ static JSVirtualMachine *sharedInstance = nil; @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 +- (id)initWithA:(int)a; +@end + +@protocol InitB +- (id)initWithA:(int)a b:(int)b; +@end + +@interface ClassA : NSObject +@end + +@interface ClassB : ClassA +@end + +@interface ClassC : ClassB +@end + +@interface ClassD : NSObject +- (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"); @@ -290,6 +447,41 @@ void testObjectiveCAPI() 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; @@ -522,7 +714,7 @@ void testObjectiveCAPI() 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 { @@ -857,6 +1049,109 @@ void testObjectiveCAPI() [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 diff --git a/Configurations/Version.xcconfig b/Configurations/Version.xcconfig index 0733d27..ec8645e 100644 --- a/Configurations/Version.xcconfig +++ b/Configurations/Version.xcconfig @@ -23,7 +23,7 @@ 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. diff --git a/GNUmakefile.list.am b/GNUmakefile.list.am index 8b79c88..7056bea 100644 --- a/GNUmakefile.list.am +++ b/GNUmakefile.list.am @@ -32,6 +32,7 @@ javascriptcore_built_nosources += \ 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 \ diff --git a/JavaScriptCore.vcxproj/JavaScriptCore.vcxproj b/JavaScriptCore.vcxproj/JavaScriptCore.vcxproj index d90ff2e..e06fbcf 100644 --- a/JavaScriptCore.vcxproj/JavaScriptCore.vcxproj +++ b/JavaScriptCore.vcxproj/JavaScriptCore.vcxproj @@ -533,6 +533,7 @@ + diff --git a/JavaScriptCore.vcxproj/JavaScriptCore.vcxproj.filters b/JavaScriptCore.vcxproj/JavaScriptCore.vcxproj.filters index 902c498..de7777f 100644 --- a/JavaScriptCore.vcxproj/JavaScriptCore.vcxproj.filters +++ b/JavaScriptCore.vcxproj/JavaScriptCore.vcxproj.filters @@ -767,6 +767,9 @@ API + + API + API diff --git a/JavaScriptCore.xcodeproj/project.pbxproj b/JavaScriptCore.xcodeproj/project.pbxproj index 75bdba5..a41a942 100644 --- a/JavaScriptCore.xcodeproj/project.pbxproj +++ b/JavaScriptCore.xcodeproj/project.pbxproj @@ -513,6 +513,7 @@ 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, ); }; }; @@ -1392,6 +1393,7 @@ 1CAA8B4B0D32C39A0041BCFF /* JavaScriptCore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JavaScriptCore.h; sourceTree = ""; }; 2600B5A4152BAAA70091EE5F /* JSStringJoiner.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSStringJoiner.cpp; sourceTree = ""; }; 2600B5A5152BAAA70091EE5F /* JSStringJoiner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSStringJoiner.h; sourceTree = ""; }; + 2A3D60CE188DD95F00C9C528 /* APICallbackFunction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = APICallbackFunction.h; sourceTree = ""; }; 449097EE0F8F81B50076A327 /* FeatureDefines.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = FeatureDefines.xcconfig; sourceTree = ""; }; 451539B812DC994500EF7AC4 /* Yarr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Yarr.h; path = yarr/Yarr.h; sourceTree = ""; }; 45E12D8806A49B0F00E9DF84 /* jsc.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 4; lastKnownFileType = sourcecode.cpp.cpp; path = jsc.cpp; sourceTree = ""; tabWidth = 4; }; @@ -2204,6 +2206,7 @@ 1432EBD70A34CAD400717B9F /* API */ = { isa = PBXGroup; children = ( + 2A3D60CE188DD95F00C9C528 /* APICallbackFunction.h */, 1482B78A0A4305AB00517CFC /* APICast.h */, 865F408710E7D56300947361 /* APIShims.h */, 1CAA8B4A0D32C39A0041BCFF /* JavaScript.h */, @@ -3248,6 +3251,7 @@ 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 */, diff --git a/assembler/MacroAssemblerARM64.h b/assembler/MacroAssemblerARM64.h index 2033e05..7c9e922 100644 --- a/assembler/MacroAssemblerARM64.h +++ b/assembler/MacroAssemblerARM64.h @@ -849,6 +849,8 @@ public: { moveToCachedReg(TrustedImmPtr(address), m_cachedMemoryTempRegister); m_assembler.ldrb(dest, memoryTempRegister, ARM64Registers::zr); + if (dest == memoryTempRegister) + m_cachedMemoryTempRegister.invalidate(); } void load8Signed(BaseIndex address, RegisterID dest) @@ -1516,8 +1518,8 @@ public: 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) @@ -1554,8 +1556,8 @@ public: 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) @@ -2396,6 +2398,9 @@ private: intptr_t addressAsInt = reinterpret_cast(address); intptr_t addressDelta = addressAsInt - currentRegisterContents; + if (dest == memoryTempRegister) + m_cachedMemoryTempRegister.invalidate(); + if (isInIntRange(addressDelta)) { if (ARM64Assembler::canEncodeSImmOffset(addressDelta)) { m_assembler.ldur(dest, memoryTempRegister, addressDelta); @@ -2417,7 +2422,10 @@ private: } move(TrustedImmPtr(address), memoryTempRegister); - m_cachedMemoryTempRegister.setValue(reinterpret_cast(address)); + if (dest == memoryTempRegister) + m_cachedMemoryTempRegister.invalidate(); + else + m_cachedMemoryTempRegister.setValue(reinterpret_cast(address)); m_assembler.ldr(dest, memoryTempRegister, ARM64Registers::zr); } diff --git a/dfg/DFGAbstractState.cpp b/dfg/DFGAbstractState.cpp index 200ed74..be83f69 100644 --- a/dfg/DFGAbstractState.cpp +++ b/dfg/DFGAbstractState.cpp @@ -1529,7 +1529,7 @@ bool AbstractState::executeEffects(unsigned indexInBlock, Node* node) // Again, sadly, we don't propagate the fact that we've done InstanceOf forNode(node).set(SpecBoolean); break; - + case Phi: case Flush: case PhantomLocal: @@ -1552,6 +1552,10 @@ bool AbstractState::executeEffects(unsigned indexInBlock, Node* node) forNode(node).makeTop(); break; + case Unreachable: + RELEASE_ASSERT_NOT_REACHED(); + break; + case ForceOSRExit: node->setCanExit(true); m_isValid = false; @@ -1777,8 +1781,7 @@ inline bool AbstractState::mergeToSuccessors(Graph& graph, BasicBlock* basicBloc } case Return: - case Throw: - case ThrowReferenceError: + case Unreachable: ASSERT(basicBlock->cfaBranchDirection == InvalidBranchDirection); return false; diff --git a/dfg/DFGArgumentsSimplificationPhase.cpp b/dfg/DFGArgumentsSimplificationPhase.cpp index 06ce701..9b78856 100644 --- a/dfg/DFGArgumentsSimplificationPhase.cpp +++ b/dfg/DFGArgumentsSimplificationPhase.cpp @@ -126,12 +126,9 @@ public: 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--;) { @@ -703,6 +700,14 @@ private: NullableHashTraits > m_argumentsAliasing; HashSet m_isLive; + void pruneObviousArgumentCreations(InlineCallFrame* inlineCallFrame) + { + ScriptExecutable* executable = jsCast(m_graph.executableFor(inlineCallFrame)); + if (m_graph.m_executablesWhoseArgumentsEscaped.contains(executable) + || executable->isStrictMode()) + m_createsArguments.add(inlineCallFrame); + } + void observeBadArgumentsUse(Node* node) { if (!node) diff --git a/dfg/DFGByteCodeParser.cpp b/dfg/DFGByteCodeParser.cpp index a76d5f2..c3e041c 100644 --- a/dfg/DFGByteCodeParser.cpp +++ b/dfg/DFGByteCodeParser.cpp @@ -3031,13 +3031,15 @@ bool ByteCodeParser::parseBlock(unsigned limit) 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: diff --git a/dfg/DFGCSEPhase.cpp b/dfg/DFGCSEPhase.cpp index 47af696..0eb29fc 100644 --- a/dfg/DFGCSEPhase.cpp +++ b/dfg/DFGCSEPhase.cpp @@ -254,9 +254,11 @@ private: 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(); @@ -327,9 +329,11 @@ private: 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: { diff --git a/dfg/DFGConstantFoldingPhase.cpp b/dfg/DFGConstantFoldingPhase.cpp index 39ac2ff..ad699e7 100644 --- a/dfg/DFGConstantFoldingPhase.cpp +++ b/dfg/DFGConstantFoldingPhase.cpp @@ -434,8 +434,7 @@ private: 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; diff --git a/dfg/DFGFixupPhase.cpp b/dfg/DFGFixupPhase.cpp index 0f280b1..f8b8fcb 100644 --- a/dfg/DFGFixupPhase.cpp +++ b/dfg/DFGFixupPhase.cpp @@ -120,6 +120,7 @@ private: case ValueToInt32: { if (node->child1()->shouldSpeculateInteger()) { setUseKindAndUnboxIfProfitable(node->child1()); + node->setOpAndDefaultFlags(Identity); break; } @@ -873,6 +874,7 @@ private: case CountExecution: case ForceOSRExit: case CheckWatchdogTimer: + case Unreachable: break; #else default: diff --git a/dfg/DFGNode.h b/dfg/DFGNode.h index fdfa294..03c9c2d 100644 --- a/dfg/DFGNode.h +++ b/dfg/DFGNode.h @@ -676,8 +676,7 @@ struct Node { case Jump: case Branch: case Return: - case Throw: - case ThrowReferenceError: + case Unreachable: return true; default: return false; diff --git a/dfg/DFGNodeType.h b/dfg/DFGNodeType.h index 9039e3f..2a47073 100644 --- a/dfg/DFGNodeType.h +++ b/dfg/DFGNodeType.h @@ -250,12 +250,15 @@ namespace JSC { namespace DFG { 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) \ \ diff --git a/dfg/DFGPredictionPropagationPhase.cpp b/dfg/DFGPredictionPropagationPhase.cpp index 356adb5..ddcc313 100644 --- a/dfg/DFGPredictionPropagationPhase.cpp +++ b/dfg/DFGPredictionPropagationPhase.cpp @@ -539,6 +539,7 @@ private: case PutGlobalVar: case PutGlobalVarCheck: case CheckWatchdogTimer: + case Unreachable: break; // These gets ignored because it doesn't do anything. diff --git a/dfg/DFGSpeculativeJIT32_64.cpp b/dfg/DFGSpeculativeJIT32_64.cpp index 0ab170d..d317495 100644 --- a/dfg/DFGSpeculativeJIT32_64.cpp +++ b/dfg/DFGSpeculativeJIT32_64.cpp @@ -1106,6 +1106,10 @@ GPRReg SpeculativeJIT::fillSpeculateCell(Edge edge) 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()); @@ -4401,7 +4405,7 @@ void SpeculativeJIT::compile(Node* 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); @@ -4957,6 +4961,10 @@ void SpeculativeJIT::compile(Node* node) noResult(node); break; + case Unreachable: + RELEASE_ASSERT_NOT_REACHED(); + break; + case Nop: case LastNodeType: RELEASE_ASSERT_NOT_REACHED(); diff --git a/dfg/DFGSpeculativeJIT64.cpp b/dfg/DFGSpeculativeJIT64.cpp index bf5361e..1785258 100644 --- a/dfg/DFGSpeculativeJIT64.cpp +++ b/dfg/DFGSpeculativeJIT64.cpp @@ -984,8 +984,6 @@ FPRReg SpeculativeJIT::fillSpeculateDouble(Edge edge) m_jit.move64ToDouble(gpr, fpr); unlock(gpr); - m_fprs.retain(fpr, virtualRegister, SpillOrderDouble); - info.fillDouble(*m_stream, fpr); return fpr; } if (isNumberConstant(edge.node())) { @@ -4798,6 +4796,10 @@ void SpeculativeJIT::compile(Node* node) // This is a no-op. noResult(node); break; + + case Unreachable: + RELEASE_ASSERT_NOT_REACHED(); + break; case Nop: RELEASE_ASSERT_NOT_REACHED(); diff --git a/heap/SlotVisitorInlines.h b/heap/SlotVisitorInlines.h index 4273a28..da338ce 100644 --- a/heap/SlotVisitorInlines.h +++ b/heap/SlotVisitorInlines.h @@ -174,6 +174,7 @@ inline void SlotVisitor::donateAndDrain() 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); diff --git a/interpreter/Interpreter.cpp b/interpreter/Interpreter.cpp index 87a9c2b..14e4cc6 100644 --- a/interpreter/Interpreter.cpp +++ b/interpreter/Interpreter.cpp @@ -765,7 +765,20 @@ NEVER_INLINE HandlerInfo* Interpreter::throwException(CallFrame*& callFrame, JSV 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); } diff --git a/parser/NodeConstructors.h b/parser/NodeConstructors.h index 47f6eea..b10a078 100644 --- a/parser/NodeConstructors.h +++ b/parser/NodeConstructors.h @@ -521,7 +521,7 @@ inline ResolveNode::ResolveNode(const JSTokenLocation& location, const Identifie } 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) diff --git a/parser/ResultType.h b/parser/ResultType.h index de4bde6..ad86c98 100644 --- a/parser/ResultType.h +++ b/parser/ResultType.h @@ -120,7 +120,20 @@ namespace JSC { 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(); diff --git a/runtime/ArrayConventions.h b/runtime/ArrayConventions.h index 3177c6c..e5ef963 100644 --- a/runtime/ArrayConventions.h +++ b/runtime/ArrayConventions.h @@ -71,6 +71,8 @@ namespace JSC { // 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 @@ -82,6 +84,11 @@ inline bool isDenseEnoughForVector(unsigned length, unsigned numValues) 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; diff --git a/runtime/JSArray.cpp b/runtime/JSArray.cpp index f97cced..2527b14 100644 --- a/runtime/JSArray.cpp +++ b/runtime/JSArray.cpp @@ -755,21 +755,21 @@ bool JSArray::shiftCountWithAnyIndexingType(ExecState* exec, unsigned startIndex // 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. @@ -790,21 +790,21 @@ bool JSArray::shiftCountWithAnyIndexingType(ExecState* exec, unsigned startIndex // 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. @@ -889,11 +889,18 @@ bool JSArray::unshiftCountWithAnyIndexingType(ExecState* exec, unsigned startInd 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); } @@ -915,10 +922,17 @@ bool JSArray::unshiftCountWithAnyIndexingType(ExecState* exec, unsigned startInd 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; } diff --git a/runtime/JSObject.cpp b/runtime/JSObject.cpp index a39daff..d18b4e2 100644 --- a/runtime/JSObject.cpp +++ b/runtime/JSObject.cpp @@ -595,7 +595,7 @@ void JSObject::notifyPresenceOfIndexedAccessors(VM& vm) 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; @@ -681,7 +681,7 @@ ArrayStorage* JSObject::createInitialArrayStorage(VM& vm) 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(); } @@ -692,14 +692,14 @@ ContiguousDoubles JSObject::convertUndecidedToDouble(VM& vm) 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(); } @@ -765,7 +765,7 @@ ContiguousDoubles JSObject::convertInt32ToDouble(VM& vm) *currentAsDouble = v.asInt32(); } - setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateDouble)); + setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateDouble), m_butterfly); return m_butterfly->contiguousDouble(); } @@ -773,7 +773,7 @@ ContiguousJSValues JSObject::convertInt32ToContiguous(VM& vm) { ASSERT(hasInt32(structure()->indexingType())); - setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateContiguous)); + setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateContiguous), m_butterfly); return m_butterfly->contiguous(); } @@ -831,7 +831,7 @@ ContiguousJSValues JSObject::genericConvertDoubleToContiguous(VM& vm) currentAsValue->setWithoutWriteBarrier(v); } - setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateContiguous)); + setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateContiguous), m_butterfly); return m_butterfly->contiguous(); } @@ -1129,7 +1129,7 @@ void JSObject::switchToSlowPutArrayStorage(VM& vm) case NonArrayWithArrayStorage: case ArrayWithArrayStorage: { Structure* newStructure = Structure::nonPropertyTransition(vm, structure(), SwitchToSlowPutArrayStorage); - setStructure(vm, newStructure); + setStructure(vm, newStructure, m_butterfly); break; } @@ -1153,7 +1153,7 @@ void JSObject::setPrototype(VM& vm, JSValue prototype) vm.prototypeMap.addPrototype(asObject(prototype)); Structure* newStructure = Structure::changePrototypeTransition(vm, structure(), prototype); - setStructure(vm, newStructure); + setStructure(vm, newStructure, m_butterfly); if (!newStructure->anyObjectInChainMayInterceptIndexedAccesses()) return; @@ -1213,7 +1213,7 @@ void JSObject::putDirectAccessor(ExecState* exec, PropertyName propertyName, JSV // 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(); @@ -1570,7 +1570,7 @@ void JSObject::seal(VM& vm) 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) @@ -1578,14 +1578,14 @@ 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 @@ -1603,7 +1603,7 @@ void JSObject::reifyStaticFunctionsForDelete(ExecState* exec) } 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()); @@ -1633,7 +1633,7 @@ bool JSObject::removeDirect(VM& vm, PropertyName propertyName) 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); @@ -1872,7 +1872,8 @@ void JSObject::putByIndexBeyondVectorLengthWithoutAttributes(ExecState* exec, un if (i >= MAX_ARRAY_INDEX - 1 || (i >= MIN_SPARSE_ARRAY_INDEX - && !isDenseEnoughForVector(i, countElements(m_butterfly)))) { + && !isDenseEnoughForVector(i, countElements(m_butterfly))) + || indexIsSufficientlyBeyondLengthForSparseMap(i, m_butterfly->vectorLength())) { ASSERT(i <= MAX_ARRAY_INDEX); ensureArrayStorageSlow(vm); SparseArrayValueMap* map = allocateSparseIndexMap(vm); @@ -1920,7 +1921,7 @@ void JSObject::putByIndexBeyondVectorLengthWithArrayStorage(ExecState* exec, uns // 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. @@ -1928,7 +1929,9 @@ void JSObject::putByIndexBeyondVectorLengthWithArrayStorage(ExecState* exec, uns 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); @@ -1995,7 +1998,7 @@ void JSObject::putByIndexBeyondVectorLength(ExecState* exec, unsigned i, JSValue 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; @@ -2075,7 +2078,8 @@ bool JSObject::putDirectIndexBeyondVectorLengthWithArrayStorage(ExecState* exec, 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); diff --git a/runtime/JSObject.h b/runtime/JSObject.h index c62dc2a..7a78a46 100644 --- a/runtime/JSObject.h +++ b/runtime/JSObject.h @@ -595,6 +595,7 @@ public: 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*); @@ -1109,7 +1110,7 @@ inline void JSObject::setButterfly(VM& vm, Butterfly* butterfly, Structure* stru { ASSERT(structure); ASSERT(!butterfly == (!structure->outOfLineCapacity() && !hasIndexingHeader(structure->indexingType()))); - setStructure(vm, structure); + setStructure(vm, structure, butterfly); m_butterfly = butterfly; } @@ -1316,7 +1317,7 @@ inline bool JSObject::putDirectInternal(VM& vm, PropertyName propertyName, JSVal 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. @@ -1344,12 +1345,18 @@ inline bool JSObject::putDirectInternal(VM& vm, PropertyName propertyName, JSVal 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; } diff --git a/runtime/JSScope.cpp b/runtime/JSScope.cpp index 69ff1e4..7ad5665 100644 --- a/runtime/JSScope.cpp +++ b/runtime/JSScope.cpp @@ -301,6 +301,14 @@ template JSObject 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()); diff --git a/runtime/RegExpMatchesArray.h b/runtime/RegExpMatchesArray.h index af747b6..c7c9420 100644 --- a/runtime/RegExpMatchesArray.h +++ b/runtime/RegExpMatchesArray.h @@ -44,7 +44,7 @@ namespace JSC { 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&); diff --git a/runtime/Structure.cpp b/runtime/Structure.cpp index f551eae..950728c 100644 --- a/runtime/Structure.cpp +++ b/runtime/Structure.cpp @@ -649,6 +649,12 @@ Structure* Structure::flattenDictionaryStructure(VM& vm, JSObject* object) } 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; } -- 2.45.2