diff options
Diffstat (limited to 'Source/JavaScriptCore/API')
56 files changed, 6675 insertions, 261 deletions
diff --git a/Source/JavaScriptCore/API/APICast.h b/Source/JavaScriptCore/API/APICast.h index f019a7a4b..fc5d71b2e 100644 --- a/Source/JavaScriptCore/API/APICast.h +++ b/Source/JavaScriptCore/API/APICast.h @@ -27,14 +27,13 @@ #define APICast_h #include "JSAPIValueWrapper.h" +#include "JSCJSValue.h" #include "JSGlobalObject.h" -#include "JSValue.h" -#include <wtf/UnusedParam.h> namespace JSC { class ExecState; class PropertyNameArray; - class JSGlobalData; + class VM; class JSObject; class JSValue; } @@ -63,46 +62,63 @@ inline JSC::ExecState* toJS(JSGlobalContextRef c) inline JSC::JSValue toJS(JSC::ExecState* exec, JSValueRef v) { ASSERT_UNUSED(exec, exec); - ASSERT(v); #if USE(JSVALUE32_64) JSC::JSCell* jsCell = reinterpret_cast<JSC::JSCell*>(const_cast<OpaqueJSValue*>(v)); if (!jsCell) - return JSC::JSValue(); + return JSC::jsNull(); + JSC::JSValue result; if (jsCell->isAPIValueWrapper()) - return JSC::jsCast<JSC::JSAPIValueWrapper*>(jsCell)->value(); - return jsCell; + result = JSC::jsCast<JSC::JSAPIValueWrapper*>(jsCell)->value(); + else + result = jsCell; #else - return JSC::JSValue::decode(reinterpret_cast<JSC::EncodedJSValue>(const_cast<OpaqueJSValue*>(v))); + JSC::JSValue result = JSC::JSValue::decode(reinterpret_cast<JSC::EncodedJSValue>(const_cast<OpaqueJSValue*>(v))); #endif + if (!result) + return JSC::jsNull(); + if (result.isCell()) + RELEASE_ASSERT(result.asCell()->methodTable()); + return result; } inline JSC::JSValue toJSForGC(JSC::ExecState* exec, JSValueRef v) { ASSERT_UNUSED(exec, exec); - ASSERT(v); #if USE(JSVALUE32_64) JSC::JSCell* jsCell = reinterpret_cast<JSC::JSCell*>(const_cast<OpaqueJSValue*>(v)); if (!jsCell) return JSC::JSValue(); - return jsCell; + JSC::JSValue result = jsCell; #else - return JSC::JSValue::decode(reinterpret_cast<JSC::EncodedJSValue>(const_cast<OpaqueJSValue*>(v))); + JSC::JSValue result = JSC::JSValue::decode(reinterpret_cast<JSC::EncodedJSValue>(const_cast<OpaqueJSValue*>(v))); #endif + if (result && result.isCell()) + RELEASE_ASSERT(result.asCell()->methodTable()); + return result; } -inline JSC::JSObject* toJS(JSObjectRef o) +// Used in JSObjectGetPrivate as that may be called during finalization +inline JSC::JSObject* uncheckedToJS(JSObjectRef o) { return reinterpret_cast<JSC::JSObject*>(o); } +inline JSC::JSObject* toJS(JSObjectRef o) +{ + JSC::JSObject* object = uncheckedToJS(o); + if (object) + RELEASE_ASSERT(object->methodTable()); + return object; +} + inline JSC::PropertyNameArray* toJS(JSPropertyNameAccumulatorRef a) { return reinterpret_cast<JSC::PropertyNameArray*>(a); } -inline JSC::JSGlobalData* toJS(JSContextGroupRef g) +inline JSC::VM* toJS(JSContextGroupRef g) { - return reinterpret_cast<JSC::JSGlobalData*>(const_cast<OpaqueJSContextGroup*>(g)); + return reinterpret_cast<JSC::VM*>(const_cast<OpaqueJSContextGroup*>(g)); } inline JSValueRef toRef(JSC::ExecState* exec, JSC::JSValue v) @@ -145,7 +161,7 @@ inline JSPropertyNameAccumulatorRef toRef(JSC::PropertyNameArray* l) return reinterpret_cast<JSPropertyNameAccumulatorRef>(l); } -inline JSContextGroupRef toRef(JSC::JSGlobalData* g) +inline JSContextGroupRef toRef(JSC::VM* g) { return reinterpret_cast<JSContextGroupRef>(g); } diff --git a/Source/JavaScriptCore/API/APIShims.h b/Source/JavaScriptCore/API/APIShims.h index ef5f10466..d8e1fb790 100644 --- a/Source/JavaScriptCore/API/APIShims.h +++ b/Source/JavaScriptCore/API/APIShims.h @@ -35,34 +35,22 @@ namespace JSC { class APIEntryShimWithoutLock { -public: - enum RefGlobalDataTag { DontRefGlobalData = 0, RefGlobalData }; - protected: - APIEntryShimWithoutLock(JSGlobalData* globalData, bool registerThread, RefGlobalDataTag shouldRefGlobalData) - : m_shouldRefGlobalData(shouldRefGlobalData) - , m_globalData(globalData) - , m_entryIdentifierTable(wtfThreadData().setCurrentIdentifierTable(globalData->identifierTable)) + APIEntryShimWithoutLock(VM* vm, bool registerThread) + : m_vm(vm) + , m_entryIdentifierTable(wtfThreadData().setCurrentIdentifierTable(vm->identifierTable)) { - if (shouldRefGlobalData) - m_globalData->ref(); - UNUSED_PARAM(registerThread); if (registerThread) - globalData->heap.machineThreads().addCurrentThread(); - m_globalData->heap.activityCallback()->synchronize(); - m_globalData->heap.sweeper()->synchronize(); + vm->heap.machineThreads().addCurrentThread(); } ~APIEntryShimWithoutLock() { wtfThreadData().setCurrentIdentifierTable(m_entryIdentifierTable); - if (m_shouldRefGlobalData) - m_globalData->deref(); } protected: - RefGlobalDataTag m_shouldRefGlobalData; - JSGlobalData* m_globalData; + RefPtr<VM> m_vm; IdentifierTable* m_entryIdentifierTable; }; @@ -70,57 +58,45 @@ class APIEntryShim : public APIEntryShimWithoutLock { public: // Normal API entry APIEntryShim(ExecState* exec, bool registerThread = true) - : APIEntryShimWithoutLock(&exec->globalData(), registerThread, RefGlobalData) + : APIEntryShimWithoutLock(&exec->vm(), registerThread) + , m_lockHolder(exec) { - init(); } - // This constructor is necessary for HeapTimer to prevent it from accidentally resurrecting - // the ref count of a "dead" JSGlobalData. - APIEntryShim(JSGlobalData* globalData, RefGlobalDataTag refGlobalData, bool registerThread = true) - : APIEntryShimWithoutLock(globalData, registerThread, refGlobalData) + // JSPropertyNameAccumulator only has a vm. + APIEntryShim(VM* vm, bool registerThread = true) + : APIEntryShimWithoutLock(vm, registerThread) + , m_lockHolder(vm) { - init(); - } - - // JSPropertyNameAccumulator only has a globalData. - APIEntryShim(JSGlobalData* globalData, bool registerThread = true) - : APIEntryShimWithoutLock(globalData, registerThread, RefGlobalData) - { - init(); } ~APIEntryShim() { - m_globalData->timeoutChecker.stop(); - m_globalData->apiLock().unlock(); + // Destroying our JSLockHolder should also destroy the VM. + m_vm.clear(); } private: - void init() - { - m_globalData->apiLock().lock(); - m_globalData->timeoutChecker.start(); - } + JSLockHolder m_lockHolder; }; class APICallbackShim { public: APICallbackShim(ExecState* exec) : m_dropAllLocks(exec) - , m_globalData(&exec->globalData()) + , m_vm(&exec->vm()) { wtfThreadData().resetCurrentIdentifierTable(); } ~APICallbackShim() { - wtfThreadData().setCurrentIdentifierTable(m_globalData->identifierTable); + wtfThreadData().setCurrentIdentifierTable(m_vm->identifierTable); } private: JSLock::DropAllLocks m_dropAllLocks; - JSGlobalData* m_globalData; + VM* m_vm; }; } diff --git a/Source/JavaScriptCore/API/JSAPIWrapperObject.h b/Source/JavaScriptCore/API/JSAPIWrapperObject.h new file mode 100644 index 000000000..909039771 --- /dev/null +++ b/Source/JavaScriptCore/API/JSAPIWrapperObject.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSAPIWrapperObject_h +#define JSAPIWrapperObject_h + +#include "JSBase.h" +#include "JSDestructibleObject.h" +#include "WeakReferenceHarvester.h" + +#if JSC_OBJC_API_ENABLED + +namespace JSC { + +class JSAPIWrapperObject : public JSDestructibleObject { +public: + typedef JSDestructibleObject Base; + + void finishCreation(VM&); + static void visitChildren(JSCell*, JSC::SlotVisitor&); + + void* wrappedObject() { return m_wrappedObject; } + void setWrappedObject(void*); + +protected: + static const unsigned StructureFlags = OverridesVisitChildren | Base::StructureFlags; + + JSAPIWrapperObject(VM&, Structure*); + +private: + void* m_wrappedObject; +}; + +} // namespace JSC + +#endif // JSC_OBJC_API_ENABLED + +#endif // JSAPIWrapperObject_h diff --git a/Source/JavaScriptCore/API/JSAPIWrapperObject.mm b/Source/JavaScriptCore/API/JSAPIWrapperObject.mm new file mode 100644 index 000000000..c06de3978 --- /dev/null +++ b/Source/JavaScriptCore/API/JSAPIWrapperObject.mm @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2013 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "JSAPIWrapperObject.h" + +#include "JSCJSValueInlines.h" +#include "JSCallbackObject.h" +#include "JSCellInlines.h" +#include "JSVirtualMachineInternal.h" +#include "SlotVisitorInlines.h" +#include "Structure.h" +#include "StructureInlines.h" + +#if JSC_OBJC_API_ENABLED + +class JSAPIWrapperObjectHandleOwner : public JSC::WeakHandleOwner { +public: + virtual void finalize(JSC::Handle<JSC::Unknown>, void*); + virtual bool isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown>, void* context, JSC::SlotVisitor&); +}; + +static JSAPIWrapperObjectHandleOwner* jsAPIWrapperObjectHandleOwner() +{ + DEFINE_STATIC_LOCAL(JSAPIWrapperObjectHandleOwner, jsWrapperObjectHandleOwner, ()); + return &jsWrapperObjectHandleOwner; +} + +void JSAPIWrapperObjectHandleOwner::finalize(JSC::Handle<JSC::Unknown> handle, void*) +{ + JSC::JSAPIWrapperObject* wrapperObject = JSC::jsCast<JSC::JSAPIWrapperObject*>(handle.get().asCell()); + if (!wrapperObject->wrappedObject()) + return; + [static_cast<id>(wrapperObject->wrappedObject()) release]; + JSC::WeakSet::deallocate(JSC::WeakImpl::asWeakImpl(handle.slot())); +} + +bool JSAPIWrapperObjectHandleOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void*, JSC::SlotVisitor& visitor) +{ + JSC::JSAPIWrapperObject* wrapperObject = JSC::jsCast<JSC::JSAPIWrapperObject*>(handle.get().asCell()); + // We use the JSGlobalObject when processing weak handles to prevent the situation where using + // the same Objective-C object in multiple global objects keeps all of the global objects alive. + if (!wrapperObject->wrappedObject()) + return false; + return JSC::Heap::isMarked(wrapperObject->structure()->globalObject()) && visitor.containsOpaqueRoot(wrapperObject->wrappedObject()); +} + +namespace JSC { + +template <> const ClassInfo JSCallbackObject<JSAPIWrapperObject>::s_info = { "JSAPIWrapperObject", &Base::s_info, 0, 0, CREATE_METHOD_TABLE(JSCallbackObject) }; + +template<> const bool JSCallbackObject<JSAPIWrapperObject>::needsDestruction = true; + +template <> +Structure* JSCallbackObject<JSAPIWrapperObject>::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue proto) +{ + return Structure::create(vm, globalObject, proto, TypeInfo(ObjectType, StructureFlags), &s_info); +} + +JSAPIWrapperObject::JSAPIWrapperObject(VM& vm, Structure* structure) + : Base(vm, structure) + , m_wrappedObject(0) +{ +} + +void JSAPIWrapperObject::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + WeakSet::allocate(this, jsAPIWrapperObjectHandleOwner(), 0); // Balanced in JSAPIWrapperObjectHandleOwner::finalize. +} + +void JSAPIWrapperObject::setWrappedObject(void* wrappedObject) +{ + ASSERT(!m_wrappedObject); + m_wrappedObject = [static_cast<id>(wrappedObject) retain]; +} + +void JSAPIWrapperObject::visitChildren(JSCell* cell, JSC::SlotVisitor& visitor) +{ + JSAPIWrapperObject* thisObject = JSC::jsCast<JSAPIWrapperObject*>(cell); + COMPILE_ASSERT(StructureFlags & OverridesVisitChildren, OverridesVisitChildrenWithoutSettingFlag); + Base::visitChildren(cell, visitor); + + if (thisObject->wrappedObject()) + scanExternalObjectGraph(cell->structure()->globalObject()->vm(), visitor, thisObject->wrappedObject()); +} + +} // namespace JSC + +#endif // JSC_OBJC_API_ENABLED diff --git a/Source/JavaScriptCore/API/JSBase.cpp b/Source/JavaScriptCore/API/JSBase.cpp index 677c68187..7669ff1a9 100644 --- a/Source/JavaScriptCore/API/JSBase.cpp +++ b/Source/JavaScriptCore/API/JSBase.cpp @@ -29,20 +29,25 @@ #include "APICast.h" #include "APIShims.h" +#include "CallFrame.h" +#include "Completion.h" +#include "InitializeThreading.h" +#include "JSGlobalObject.h" +#include "JSLock.h" +#include "JSObject.h" #include "OpaqueJSString.h" +#include "Operations.h" #include "SourceCode.h" -#include <interpreter/CallFrame.h> -#include <runtime/InitializeThreading.h> -#include <runtime/Completion.h> -#include <runtime/JSGlobalObject.h> -#include <runtime/JSLock.h> -#include <runtime/JSObject.h> #include <wtf/text/StringHash.h> using namespace JSC; JSValueRef JSEvaluateScript(JSContextRef ctx, JSStringRef script, JSObjectRef thisObject, JSStringRef sourceURL, int startingLineNumber, JSValueRef* exception) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -70,6 +75,10 @@ JSValueRef JSEvaluateScript(JSContextRef ctx, JSStringRef script, JSObjectRef th bool JSCheckScriptSyntax(JSContextRef ctx, JSStringRef script, JSStringRef sourceURL, int startingLineNumber, JSValueRef* exception) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return false; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -100,12 +109,28 @@ void JSGarbageCollect(JSContextRef ctx) ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec, false); - exec->globalData().heap.reportAbandonedObjectGraph(); + exec->vm().heap.reportAbandonedObjectGraph(); } void JSReportExtraMemoryCost(JSContextRef ctx, size_t size) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return; + } + ExecState* exec = toJS(ctx); + APIEntryShim entryShim(exec); + exec->vm().heap.reportExtraMemoryCost(size); +} + +extern "C" JS_EXPORT void JSSynchronousGarbageCollectForDebugging(JSContextRef); + +void JSSynchronousGarbageCollectForDebugging(JSContextRef ctx) +{ + if (!ctx) + return; + ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); - exec->globalData().heap.reportExtraMemoryCost(size); + exec->vm().heap.collectAllGarbage(); } diff --git a/Source/JavaScriptCore/API/JSBase.h b/Source/JavaScriptCore/API/JSBase.h index f46a41755..50e8f1e64 100644 --- a/Source/JavaScriptCore/API/JSBase.h +++ b/Source/JavaScriptCore/API/JSBase.h @@ -30,6 +30,10 @@ #include <stdbool.h> #endif +#ifdef __OBJC__ +#import <Foundation/Foundation.h> +#endif + /* JavaScript engine interface */ /*! @typedef JSContextGroupRef A group that associates JavaScript contexts with one another. Contexts in the same group may share and exchange JavaScript objects. */ @@ -135,4 +139,9 @@ JS_EXPORT void JSGarbageCollect(JSContextRef ctx); } #endif +/* Enable the Objective-C API for platforms with a modern runtime. */ +#if !defined(JSC_OBJC_API_ENABLED) +#define JSC_OBJC_API_ENABLED (defined(__clang__) && defined(__APPLE__) && defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 && !defined(__i386__)) +#endif + #endif /* JSBase_h */ diff --git a/Source/JavaScriptCore/API/JSCTestRunnerUtils.cpp b/Source/JavaScriptCore/API/JSCTestRunnerUtils.cpp new file mode 100644 index 000000000..394246711 --- /dev/null +++ b/Source/JavaScriptCore/API/JSCTestRunnerUtils.cpp @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#include "config.h" +#include "JSCTestRunnerUtils.h" + +#include "APICast.h" +#include "CodeBlock.h" +#include "Operations.h" + +namespace JSC { + +static FunctionExecutable* getExecutable(JSContextRef context, JSValueRef theFunctionValueRef) +{ + ExecState* exec = toJS(context); + JSValue theFunctionValue = toJS(exec, theFunctionValueRef); + + JSFunction* theFunction = jsDynamicCast<JSFunction*>(theFunctionValue); + if (!theFunction) + return 0; + + FunctionExecutable* executable = jsDynamicCast<FunctionExecutable*>( + theFunction->executable()); + return executable; +} + +JSValueRef numberOfDFGCompiles(JSContextRef context, JSValueRef theFunctionValueRef) +{ + if (FunctionExecutable* executable = getExecutable(context, theFunctionValueRef)) { + CodeBlock* baselineCodeBlock = executable->baselineCodeBlockFor(CodeForCall); + + if (!baselineCodeBlock) + return JSValueMakeNumber(context, 0); + + return JSValueMakeNumber(context, baselineCodeBlock->numberOfDFGCompiles()); + } + + return JSValueMakeUndefined(context); +} + +JSValueRef setNeverInline(JSContextRef context, JSValueRef theFunctionValueRef) +{ + if (FunctionExecutable* executable = getExecutable(context, theFunctionValueRef)) + executable->setNeverInline(true); + + return JSValueMakeUndefined(context); +} + +} // namespace JSC + diff --git a/Source/JavaScriptCore/API/JSCTestRunnerUtils.h b/Source/JavaScriptCore/API/JSCTestRunnerUtils.h new file mode 100644 index 000000000..aaecdd5c9 --- /dev/null +++ b/Source/JavaScriptCore/API/JSCTestRunnerUtils.h @@ -0,0 +1,39 @@ +/* + * 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 JSCTestRunnerUtils_h +#define JSCTestRunnerUtils_h + +#include <JavaScriptCore/JSContextRef.h> +#include <JavaScriptCore/JSValueRef.h> + +namespace JSC { + +JS_EXPORT_PRIVATE JSValueRef numberOfDFGCompiles(JSContextRef, JSValueRef theFunction); +JS_EXPORT_PRIVATE JSValueRef setNeverInline(JSContextRef, JSValueRef theFunction); + +} // namespace JSC + +#endif // JSCTestRunnerUtils_h diff --git a/Source/JavaScriptCore/API/JSCallbackConstructor.cpp b/Source/JavaScriptCore/API/JSCallbackConstructor.cpp index c43182e8f..8340c10b4 100644 --- a/Source/JavaScriptCore/API/JSCallbackConstructor.cpp +++ b/Source/JavaScriptCore/API/JSCallbackConstructor.cpp @@ -28,10 +28,11 @@ #include "APIShims.h" #include "APICast.h" -#include <runtime/Error.h> -#include <runtime/JSGlobalObject.h> -#include <runtime/JSLock.h> -#include <runtime/ObjectPrototype.h> +#include "Error.h" +#include "JSGlobalObject.h" +#include "JSLock.h" +#include "ObjectPrototype.h" +#include "Operations.h" #include <wtf/Vector.h> namespace JSC { @@ -39,7 +40,7 @@ namespace JSC { const ClassInfo JSCallbackConstructor::s_info = { "CallbackConstructor", &Base::s_info, 0, 0, CREATE_METHOD_TABLE(JSCallbackConstructor) }; JSCallbackConstructor::JSCallbackConstructor(JSGlobalObject* globalObject, Structure* structure, JSClassRef jsClass, JSObjectCallAsConstructorCallback callback) - : JSDestructibleObject(globalObject->globalData(), structure) + : JSDestructibleObject(globalObject->vm(), structure) , m_class(jsClass) , m_callback(callback) { @@ -47,7 +48,7 @@ JSCallbackConstructor::JSCallbackConstructor(JSGlobalObject* globalObject, Struc void JSCallbackConstructor::finishCreation(JSGlobalObject* globalObject, JSClassRef jsClass) { - Base::finishCreation(globalObject->globalData()); + Base::finishCreation(globalObject->vm()); ASSERT(inherits(&s_info)); if (m_class) JSClassRetain(jsClass); @@ -72,10 +73,11 @@ static EncodedJSValue JSC_HOST_CALL constructJSCallback(ExecState* exec) JSObjectCallAsConstructorCallback callback = jsCast<JSCallbackConstructor*>(constructor)->callback(); if (callback) { - int argumentCount = static_cast<int>(exec->argumentCount()); - Vector<JSValueRef, 16> arguments(argumentCount); - for (int i = 0; i < argumentCount; i++) - arguments[i] = toRef(exec, exec->argument(i)); + 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; diff --git a/Source/JavaScriptCore/API/JSCallbackConstructor.h b/Source/JavaScriptCore/API/JSCallbackConstructor.h index 2bb4d07af..72100e672 100644 --- a/Source/JavaScriptCore/API/JSCallbackConstructor.h +++ b/Source/JavaScriptCore/API/JSCallbackConstructor.h @@ -48,9 +48,9 @@ public: JSObjectCallAsConstructorCallback callback() const { return m_callback; } static const ClassInfo s_info; - static Structure* createStructure(JSGlobalData& globalData, JSGlobalObject* globalObject, JSValue proto) + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue proto) { - return Structure::create(globalData, globalObject, proto, TypeInfo(ObjectType, StructureFlags), &s_info); + return Structure::create(vm, globalObject, proto, TypeInfo(ObjectType, StructureFlags), &s_info); } protected: diff --git a/Source/JavaScriptCore/API/JSCallbackFunction.cpp b/Source/JavaScriptCore/API/JSCallbackFunction.cpp index aada87bfa..c29b9077c 100644 --- a/Source/JavaScriptCore/API/JSCallbackFunction.cpp +++ b/Source/JavaScriptCore/API/JSCallbackFunction.cpp @@ -29,11 +29,13 @@ #include "APIShims.h" #include "APICast.h" #include "CodeBlock.h" +#include "Error.h" #include "ExceptionHelpers.h" -#include "JSFunction.h" #include "FunctionPrototype.h" -#include <runtime/JSGlobalObject.h> -#include <runtime/JSLock.h> +#include "JSFunction.h" +#include "JSGlobalObject.h" +#include "JSLock.h" +#include "Operations.h" #include <wtf/Vector.h> namespace JSC { @@ -42,28 +44,36 @@ ASSERT_HAS_TRIVIAL_DESTRUCTOR(JSCallbackFunction); const ClassInfo JSCallbackFunction::s_info = { "CallbackFunction", &InternalFunction::s_info, 0, 0, CREATE_METHOD_TABLE(JSCallbackFunction) }; -JSCallbackFunction::JSCallbackFunction(JSGlobalObject* globalObject, JSObjectCallAsFunctionCallback callback) - : InternalFunction(globalObject, globalObject->callbackFunctionStructure()) +JSCallbackFunction::JSCallbackFunction(JSGlobalObject* globalObject, Structure* structure, JSObjectCallAsFunctionCallback callback) + : InternalFunction(globalObject, structure) , m_callback(callback) { } -void JSCallbackFunction::finishCreation(JSGlobalData& globalData, const String& name) +void JSCallbackFunction::finishCreation(VM& vm, const String& name) { - Base::finishCreation(globalData, name); + Base::finishCreation(vm, name); ASSERT(inherits(&s_info)); } +JSCallbackFunction* JSCallbackFunction::create(ExecState* exec, JSGlobalObject* globalObject, JSObjectCallAsFunctionCallback callback, const String& name) +{ + JSCallbackFunction* function = new (NotNull, allocateCell<JSCallbackFunction>(*exec->heap())) JSCallbackFunction(globalObject, globalObject->callbackFunctionStructure(), callback); + function->finishCreation(exec->vm(), name); + return function; +} + EncodedJSValue JSCallbackFunction::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(argumentCount); - for (int i = 0; i < argumentCount; i++) - arguments[i] = toRef(exec, exec->argument(i)); + 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; diff --git a/Source/JavaScriptCore/API/JSCallbackFunction.h b/Source/JavaScriptCore/API/JSCallbackFunction.h index 50630b550..885ef949d 100644 --- a/Source/JavaScriptCore/API/JSCallbackFunction.h +++ b/Source/JavaScriptCore/API/JSCallbackFunction.h @@ -33,31 +33,27 @@ namespace JSC { class JSCallbackFunction : public InternalFunction { protected: - JSCallbackFunction(JSGlobalObject*, JSObjectCallAsFunctionCallback); - void finishCreation(JSGlobalData&, const String& name); + JSCallbackFunction(JSGlobalObject*, Structure*, JSObjectCallAsFunctionCallback); + void finishCreation(VM&, const String& name); public: typedef InternalFunction Base; - static JSCallbackFunction* create(ExecState* exec, JSGlobalObject* globalObject, JSObjectCallAsFunctionCallback callback, const String& name) - { - JSCallbackFunction* function = new (NotNull, allocateCell<JSCallbackFunction>(*exec->heap())) JSCallbackFunction(globalObject, callback); - function->finishCreation(exec->globalData(), name); - return function; - } + static JSCallbackFunction* create(ExecState*, JSGlobalObject*, JSObjectCallAsFunctionCallback, const String& name); static const ClassInfo s_info; // InternalFunction mish-mashes constructor and function behavior -- we should // refactor the code so this override isn't necessary - static Structure* createStructure(JSGlobalData& globalData, JSGlobalObject* globalObject, JSValue proto) + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue proto) { - return Structure::create(globalData, globalObject, proto, TypeInfo(ObjectType, StructureFlags), &s_info); + return Structure::create(vm, globalObject, proto, TypeInfo(ObjectType, StructureFlags), &s_info); } -private: +protected: static CallType getCallData(JSCell*, CallData&); +private: static EncodedJSValue JSC_HOST_CALL call(ExecState*); JSObjectCallAsFunctionCallback m_callback; diff --git a/Source/JavaScriptCore/API/JSCallbackObject.cpp b/Source/JavaScriptCore/API/JSCallbackObject.cpp index 76866feb0..7436e71f7 100644 --- a/Source/JavaScriptCore/API/JSCallbackObject.cpp +++ b/Source/JavaScriptCore/API/JSCallbackObject.cpp @@ -28,6 +28,7 @@ #include "JSCallbackObject.h" #include "Heap.h" +#include "Operations.h" #include <wtf/text/StringHash.h> namespace JSC { @@ -40,30 +41,24 @@ template<> const bool JSCallbackObject<JSDestructibleObject>::needsDestruction = template<> const bool JSCallbackObject<JSGlobalObject>::needsDestruction = false; template<> -JSCallbackObject<JSGlobalObject>* JSCallbackObject<JSGlobalObject>::create(JSGlobalData& globalData, JSClassRef classRef, Structure* structure) +JSCallbackObject<JSGlobalObject>* JSCallbackObject<JSGlobalObject>::create(VM& vm, JSClassRef classRef, Structure* structure) { - JSCallbackObject<JSGlobalObject>* callbackObject = new (NotNull, allocateCell<JSCallbackObject<JSGlobalObject> >(globalData.heap)) JSCallbackObject(globalData, classRef, structure); - callbackObject->finishCreation(globalData); - globalData.heap.addFinalizer(callbackObject, destroy); + JSCallbackObject<JSGlobalObject>* callbackObject = new (NotNull, allocateCell<JSCallbackObject<JSGlobalObject> >(vm.heap)) JSCallbackObject(vm, classRef, structure); + callbackObject->finishCreation(vm); + vm.heap.addFinalizer(callbackObject, destroy); return callbackObject; } template <> -Structure* JSCallbackObject<JSDestructibleObject>::createStructure(JSGlobalData& globalData, JSGlobalObject* globalObject, JSValue proto) +Structure* JSCallbackObject<JSDestructibleObject>::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue proto) { - return Structure::create(globalData, globalObject, proto, TypeInfo(ObjectType, StructureFlags), &s_info); + return Structure::create(vm, globalObject, proto, TypeInfo(ObjectType, StructureFlags), &s_info); } template <> -Structure* JSCallbackObject<JSGlobalObject>::createStructure(JSGlobalData& globalData, JSGlobalObject* globalObject, JSValue proto) +Structure* JSCallbackObject<JSGlobalObject>::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue proto) { - return Structure::create(globalData, globalObject, proto, TypeInfo(GlobalObjectType, StructureFlags), &s_info); -} - -template <class Parent> -void JSCallbackObject<Parent>::destroy(JSCell* cell) -{ - static_cast<JSCallbackObject*>(cell)->JSCallbackObject::~JSCallbackObject(); + return Structure::create(vm, globalObject, proto, TypeInfo(GlobalObjectType, StructureFlags), &s_info); } void JSCallbackObjectData::finalize(Handle<Unknown> handle, void* context) diff --git a/Source/JavaScriptCore/API/JSCallbackObject.h b/Source/JavaScriptCore/API/JSCallbackObject.h index 3acf2ef10..16d8aa2c6 100644 --- a/Source/JavaScriptCore/API/JSCallbackObject.h +++ b/Source/JavaScriptCore/API/JSCallbackObject.h @@ -54,11 +54,11 @@ struct JSCallbackObjectData : WeakHandleOwner { return m_privateProperties->getPrivateProperty(propertyName); } - void setPrivateProperty(JSGlobalData& globalData, JSCell* owner, const Identifier& propertyName, JSValue value) + void setPrivateProperty(VM& vm, JSCell* owner, const Identifier& propertyName, JSValue value) { if (!m_privateProperties) m_privateProperties = adoptPtr(new JSPrivatePropertyMap); - m_privateProperties->setPrivateProperty(globalData, owner, propertyName, value); + m_privateProperties->setPrivateProperty(vm, owner, propertyName, value); } void deletePrivateProperty(const Identifier& propertyName) @@ -86,10 +86,10 @@ struct JSCallbackObjectData : WeakHandleOwner { return location->value.get(); } - void setPrivateProperty(JSGlobalData& globalData, JSCell* owner, const Identifier& propertyName, JSValue value) + void setPrivateProperty(VM& vm, JSCell* owner, const Identifier& propertyName, JSValue value) { WriteBarrier<Unknown> empty; - m_propertyMap.add(propertyName.impl(), empty).iterator->value.set(globalData, owner, value); + m_propertyMap.add(propertyName.impl(), empty).iterator->value.set(vm, owner, value); } void deletePrivateProperty(const Identifier& propertyName) @@ -118,10 +118,10 @@ template <class Parent> class JSCallbackObject : public Parent { protected: JSCallbackObject(ExecState*, Structure*, JSClassRef, void* data); - JSCallbackObject(JSGlobalData&, JSClassRef, Structure*); + JSCallbackObject(VM&, JSClassRef, Structure*); void finishCreation(ExecState*); - void finishCreation(JSGlobalData&); + void finishCreation(VM&); public: typedef Parent Base; @@ -133,10 +133,13 @@ public: callbackObject->finishCreation(exec); return callbackObject; } - static JSCallbackObject<Parent>* create(JSGlobalData&, JSClassRef, Structure*); + static JSCallbackObject<Parent>* create(VM&, JSClassRef, Structure*); static const bool needsDestruction; - static void destroy(JSCell*); + static void destroy(JSCell* cell) + { + static_cast<JSCallbackObject*>(cell)->JSCallbackObject::~JSCallbackObject(); + } void setPrivate(void* data); void* getPrivate(); @@ -146,16 +149,16 @@ public: JSClassRef classRef() const { return m_callbackObjectData->jsClass; } bool inherits(JSClassRef) const; - static Structure* createStructure(JSGlobalData&, JSGlobalObject*, JSValue); + static Structure* createStructure(VM&, JSGlobalObject*, JSValue); JSValue getPrivateProperty(const Identifier& propertyName) const { return m_callbackObjectData->getPrivateProperty(propertyName); } - void setPrivateProperty(JSGlobalData& globalData, const Identifier& propertyName, JSValue value) + void setPrivateProperty(VM& vm, const Identifier& propertyName, JSValue value) { - m_callbackObjectData->setPrivateProperty(globalData, this, propertyName, value); + m_callbackObjectData->setPrivateProperty(vm, this, propertyName, value); } void deletePrivateProperty(const Identifier& propertyName) @@ -178,6 +181,7 @@ private: static bool getOwnPropertyDescriptor(JSObject*, ExecState*, PropertyName, PropertyDescriptor&); static void put(JSCell*, ExecState*, PropertyName, JSValue, PutPropertySlot&); + static void putByIndex(JSCell*, ExecState*, unsigned, JSValue, bool shouldThrow); static bool deleteProperty(JSCell*, ExecState*, PropertyName); static bool deletePropertyByIndex(JSCell*, ExecState*, unsigned); diff --git a/Source/JavaScriptCore/API/JSCallbackObjectFunctions.h b/Source/JavaScriptCore/API/JSCallbackObjectFunctions.h index 1a063e620..b92e3161e 100644 --- a/Source/JavaScriptCore/API/JSCallbackObjectFunctions.h +++ b/Source/JavaScriptCore/API/JSCallbackObjectFunctions.h @@ -51,7 +51,7 @@ inline JSCallbackObject<Parent>* JSCallbackObject<Parent>::asCallbackObject(JSVa template <class Parent> JSCallbackObject<Parent>::JSCallbackObject(ExecState* exec, Structure* structure, JSClassRef jsClass, void* data) - : Parent(exec->globalData(), structure) + : Parent(exec->vm(), structure) , m_callbackObjectData(adoptPtr(new JSCallbackObjectData(data, jsClass))) { } @@ -59,8 +59,8 @@ JSCallbackObject<Parent>::JSCallbackObject(ExecState* exec, Structure* structure // Global object constructor. // FIXME: Move this into a separate JSGlobalCallbackObject class derived from this one. template <class Parent> -JSCallbackObject<Parent>::JSCallbackObject(JSGlobalData& globalData, JSClassRef jsClass, Structure* structure) - : Parent(globalData, structure) +JSCallbackObject<Parent>::JSCallbackObject(VM& vm, JSClassRef jsClass, Structure* structure) + : Parent(vm, structure) , m_callbackObjectData(adoptPtr(new JSCallbackObjectData(0, jsClass))) { } @@ -68,18 +68,18 @@ JSCallbackObject<Parent>::JSCallbackObject(JSGlobalData& globalData, JSClassRef template <class Parent> void JSCallbackObject<Parent>::finishCreation(ExecState* exec) { - Base::finishCreation(exec->globalData()); + Base::finishCreation(exec->vm()); ASSERT(Parent::inherits(&s_info)); init(exec); } // This is just for Global object, so we can assume that Base::finishCreation is JSGlobalObject::finishCreation. template <class Parent> -void JSCallbackObject<Parent>::finishCreation(JSGlobalData& globalData) +void JSCallbackObject<Parent>::finishCreation(VM& vm) { ASSERT(Parent::inherits(&s_info)); ASSERT(Parent::isGlobalObject()); - Base::finishCreation(globalData); + Base::finishCreation(vm); init(jsCast<JSGlobalObject*>(this)->globalExec()); } @@ -216,7 +216,7 @@ template <class Parent> bool JSCallbackObject<Parent>::getOwnPropertyDescriptor(JSObject* object, ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor) { JSCallbackObject* thisObject = jsCast<JSCallbackObject*>(object); - PropertySlot slot; + PropertySlot slot(thisObject); if (thisObject->methodTable()->getOwnPropertySlot(thisObject, exec, propertyName, slot)) { // Ideally we should return an access descriptor, but returning a value descriptor is better than nothing. JSValue value = slot.getValue(exec, propertyName); @@ -283,7 +283,7 @@ void JSCallbackObject<Parent>::put(JSCell* cell, ExecState* exec, PropertyName p if (StaticFunctionEntry* entry = staticFunctions->get(name)) { if (entry->attributes & kJSPropertyAttributeReadOnly) return; - thisObject->JSCallbackObject<Parent>::putDirect(exec->globalData(), propertyName, value); // put as override property + thisObject->JSCallbackObject<Parent>::putDirect(exec->vm(), propertyName, value); // put as override property return; } } @@ -294,6 +294,65 @@ void JSCallbackObject<Parent>::put(JSCell* cell, ExecState* exec, PropertyName p } template <class Parent> +void JSCallbackObject<Parent>::putByIndex(JSCell* cell, ExecState* exec, unsigned propertyIndex, JSValue value, bool shouldThrow) +{ + JSCallbackObject* thisObject = jsCast<JSCallbackObject*>(cell); + JSContextRef ctx = toRef(exec); + JSObjectRef thisRef = toRef(thisObject); + RefPtr<OpaqueJSString> propertyNameRef; + JSValueRef valueRef = toRef(exec, value); + Identifier propertyName = Identifier(exec, String::number(propertyIndex)); + + for (JSClassRef jsClass = thisObject->classRef(); jsClass; jsClass = jsClass->parentClass) { + if (JSObjectSetPropertyCallback setProperty = jsClass->setProperty) { + if (!propertyNameRef) + propertyNameRef = OpaqueJSString::create(propertyName.impl()); + JSValueRef exception = 0; + bool result; + { + APICallbackShim callbackShim(exec); + result = setProperty(ctx, thisRef, propertyNameRef.get(), valueRef, &exception); + } + if (exception) + throwError(exec, toJS(exec, exception)); + if (result || exception) + return; + } + + if (OpaqueJSClassStaticValuesTable* staticValues = jsClass->staticValues(exec)) { + if (StaticValueEntry* entry = staticValues->get(propertyName.impl())) { + if (entry->attributes & kJSPropertyAttributeReadOnly) + return; + if (JSObjectSetPropertyCallback setProperty = entry->setProperty) { + if (!propertyNameRef) + propertyNameRef = OpaqueJSString::create(propertyName.impl()); + JSValueRef exception = 0; + bool result; + { + APICallbackShim callbackShim(exec); + result = setProperty(ctx, thisRef, propertyNameRef.get(), valueRef, &exception); + } + if (exception) + throwError(exec, toJS(exec, exception)); + if (result || exception) + return; + } + } + } + + if (OpaqueJSClassStaticFunctionsTable* staticFunctions = jsClass->staticFunctions(exec)) { + if (StaticFunctionEntry* entry = staticFunctions->get(propertyName.impl())) { + if (entry->attributes & kJSPropertyAttributeReadOnly) + return; + break; + } + } + } + + return Parent::putByIndex(thisObject, exec, propertyIndex, value, shouldThrow); +} + +template <class Parent> bool JSCallbackObject<Parent>::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName) { JSCallbackObject* thisObject = jsCast<JSCallbackObject*>(cell); @@ -368,10 +427,11 @@ EncodedJSValue JSCallbackObject<Parent>::construct(ExecState* exec) for (JSClassRef jsClass = jsCast<JSCallbackObject<Parent>*>(constructor)->classRef(); jsClass; jsClass = jsClass->parentClass) { if (JSObjectCallAsConstructorCallback callAsConstructor = jsClass->callAsConstructor) { - int argumentCount = static_cast<int>(exec->argumentCount()); - Vector<JSValueRef, 16> arguments(argumentCount); - for (int i = 0; i < argumentCount; i++) - arguments[i] = toRef(exec, exec->argument(i)); + 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; JSObject* result; { @@ -384,7 +444,7 @@ EncodedJSValue JSCallbackObject<Parent>::construct(ExecState* exec) } } - ASSERT_NOT_REACHED(); // getConstructData should prevent us from reaching here + RELEASE_ASSERT_NOT_REACHED(); // getConstructData should prevent us from reaching here return JSValue::encode(JSValue()); } @@ -434,10 +494,11 @@ EncodedJSValue JSCallbackObject<Parent>::call(ExecState* exec) for (JSClassRef jsClass = jsCast<JSCallbackObject<Parent>*>(toJS(functionRef))->classRef(); jsClass; jsClass = jsClass->parentClass) { if (JSObjectCallAsFunctionCallback callAsFunction = jsClass->callAsFunction) { - int argumentCount = static_cast<int>(exec->argumentCount()); - Vector<JSValueRef, 16> arguments(argumentCount); - for (int i = 0; i < argumentCount; i++) - arguments[i] = toRef(exec, exec->argument(i)); + 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; JSValue result; { @@ -450,7 +511,7 @@ EncodedJSValue JSCallbackObject<Parent>::call(ExecState* exec) } } - ASSERT_NOT_REACHED(); // getCallData should prevent us from reaching here + RELEASE_ASSERT_NOT_REACHED(); // getCallData should prevent us from reaching here return JSValue::encode(JSValue()); } @@ -508,10 +569,10 @@ void* JSCallbackObject<Parent>::getPrivate() template <class Parent> bool JSCallbackObject<Parent>::inherits(JSClassRef c) const { - for (JSClassRef jsClass = classRef(); jsClass; jsClass = jsClass->parentClass) + for (JSClassRef jsClass = classRef(); jsClass; jsClass = jsClass->parentClass) { if (jsClass == c) return true; - + } return false; } @@ -566,7 +627,7 @@ JSValue JSCallbackObject<Parent>::staticFunctionGetter(ExecState* exec, JSValue if (JSObjectCallAsFunctionCallback callAsFunction = entry->callAsFunction) { JSObject* o = JSCallbackFunction::create(exec, thisObj->globalObject(), callAsFunction, name); - thisObj->putDirect(exec->globalData(), propertyName, o, entry->attributes); + thisObj->putDirect(exec->vm(), propertyName, o, entry->attributes); return o; } } diff --git a/Source/JavaScriptCore/API/JSClassRef.cpp b/Source/JavaScriptCore/API/JSClassRef.cpp index f30b02a2a..c77f63cf9 100644 --- a/Source/JavaScriptCore/API/JSClassRef.cpp +++ b/Source/JavaScriptCore/API/JSClassRef.cpp @@ -27,12 +27,13 @@ #include "JSClassRef.h" #include "APICast.h" +#include "Identifier.h" +#include "InitializeThreading.h" #include "JSCallbackObject.h" +#include "JSGlobalObject.h" #include "JSObjectRef.h" -#include <runtime/InitializeThreading.h> -#include <runtime/JSGlobalObject.h> -#include <runtime/ObjectPrototype.h> -#include <runtime/Identifier.h> +#include "ObjectPrototype.h" +#include "Operations.h" #include <wtf/text/StringHash.h> #include <wtf/unicode/UTF8.h> @@ -126,7 +127,7 @@ PassRefPtr<OpaqueJSClass> OpaqueJSClass::create(const JSClassDefinition* clientD return adoptRef(new OpaqueJSClass(&definition, protoClass.get())); } -OpaqueJSClassContextData::OpaqueJSClassContextData(JSC::JSGlobalData&, OpaqueJSClass* jsClass) +OpaqueJSClassContextData::OpaqueJSClassContextData(JSC::VM&, OpaqueJSClass* jsClass) : m_class(jsClass) { if (jsClass->m_staticValues) { @@ -150,9 +151,9 @@ OpaqueJSClassContextData::OpaqueJSClassContextData(JSC::JSGlobalData&, OpaqueJSC OpaqueJSClassContextData& OpaqueJSClass::contextData(ExecState* exec) { - OwnPtr<OpaqueJSClassContextData>& contextData = exec->globalData().opaqueJSClassData.add(this, nullptr).iterator->value; + OwnPtr<OpaqueJSClassContextData>& contextData = exec->lexicalGlobalObject()->opaqueJSClassData().add(this, nullptr).iterator->value; if (!contextData) - contextData = adoptPtr(new OpaqueJSClassContextData(exec->globalData(), this)); + contextData = adoptPtr(new OpaqueJSClassContextData(exec->vm(), this)); return *contextData; } @@ -202,7 +203,7 @@ JSObject* OpaqueJSClass::prototype(ExecState* exec) JSObject* prototype = JSCallbackObject<JSDestructibleObject>::create(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->callbackObjectStructure(), prototypeClass, &jsClassData); // set jsClassData as the object's private data, so it can clear our reference on destruction if (parentClass) { if (JSObject* parentPrototype = parentClass->prototype(exec)) - prototype->setPrototype(exec->globalData(), parentPrototype); + prototype->setPrototype(exec->vm(), parentPrototype); } jsClassData.cachedPrototype = PassWeak<JSObject>(prototype); diff --git a/Source/JavaScriptCore/API/JSClassRef.h b/Source/JavaScriptCore/API/JSClassRef.h index 44d5d11b9..7bf18e758 100644 --- a/Source/JavaScriptCore/API/JSClassRef.h +++ b/Source/JavaScriptCore/API/JSClassRef.h @@ -26,10 +26,9 @@ #ifndef JSClassRef_h #define JSClassRef_h -#include "JSObjectRef.h" +#include <JavaScriptCore/JSObjectRef.h> #include "Weak.h" -#include "JSObject.h" #include "Protect.h" #include <wtf/HashMap.h> #include <wtf/text/WTFString.h> @@ -69,14 +68,14 @@ struct OpaqueJSClass; struct OpaqueJSClassContextData { WTF_MAKE_NONCOPYABLE(OpaqueJSClassContextData); WTF_MAKE_FAST_ALLOCATED; public: - OpaqueJSClassContextData(JSC::JSGlobalData&, OpaqueJSClass*); + OpaqueJSClassContextData(JSC::VM&, OpaqueJSClass*); // It is necessary to keep OpaqueJSClass alive because of the following rare scenario: - // 1. A class is created and used, so its context data is stored in JSGlobalData hash map. + // 1. A class is created and used, so its context data is stored in VM hash map. // 2. The class is released, and when all JS objects that use it are collected, OpaqueJSClass // is deleted (that's the part prevented by this RefPtr). // 3. Another class is created at the same address. - // 4. When it is used, the old context data is found in JSGlobalData and used. + // 4. When it is used, the old context data is found in VM and used. RefPtr<OpaqueJSClass> m_class; OwnPtr<OpaqueJSClassStaticValuesTable> staticValues; @@ -87,7 +86,7 @@ public: struct OpaqueJSClass : public ThreadSafeRefCounted<OpaqueJSClass> { static PassRefPtr<OpaqueJSClass> create(const JSClassDefinition*); static PassRefPtr<OpaqueJSClass> createNoAutomaticPrototype(const JSClassDefinition*); - ~OpaqueJSClass(); + JS_EXPORT_PRIVATE ~OpaqueJSClass(); String className(); OpaqueJSClassStaticValuesTable* staticValues(JSC::ExecState*); diff --git a/Source/JavaScriptCore/API/JSContext.h b/Source/JavaScriptCore/API/JSContext.h new file mode 100644 index 000000000..ef3e51f17 --- /dev/null +++ b/Source/JavaScriptCore/API/JSContext.h @@ -0,0 +1,129 @@ +/* + * 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 JSContext_h +#define JSContext_h + +#include <JavaScriptCore/JavaScript.h> + +#if JSC_OBJC_API_ENABLED + +@class JSVirtualMachine, JSValue; + +// An instance of JSContext represents a JavaScript execution environment. All +// JavaScript execution takes place within a context. +// JSContext is also used to manage the life-cycle of objects within the +// JavaScript virtual machine. Every instance of JSValue is associated with a +// JSContext via a strong reference. The JSValue will keep the JSContext it +// references alive so long as the JSValue remains alive. When all of the JSValues +// that reference a particular JSContext have been deallocated the JSContext +// will be deallocated unless it has been previously retained. + +NS_CLASS_AVAILABLE(10_9, NA) +@interface JSContext : NSObject + +// Create a JSContext. +- (id)init; +// Create a JSContext in the specified virtual machine. +- (id)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine; + +// Evaluate a string of JavaScript code. +- (JSValue *)evaluateScript:(NSString *)script; + +// This method retrieves the global object of the JavaScript execution context. +// Instances of JSContext originating from WebKit will return a reference to the +// WindowProxy object. +- (JSValue *)globalObject; + +// This method may be called from within an Objective-C block or method invoked +// as a callback from JavaScript to retrieve the callback's context. Outside of +// a callback from JavaScript this method will return nil. ++ (JSContext *)currentContext; +// This method may be called from within an Objective-C block or method invoked +// as a callback from JavaScript to retrieve the callback's this value. Outside +// of a callback from JavaScript this method will return nil. ++ (JSValue *)currentThis; +// This method may be called from within an Objective-C block or method invoked +// as a callback from JavaScript to retrieve the callback's arguments, objects +// in the returned array are instances of JSValue. Outside of a callback from +// JavaScript this method will return nil. ++ (NSArray *)currentArguments; + +// The "exception" property may be used to throw an exception to JavaScript. +// Before a callback is made from JavaScript to an Objective-C block or method, +// the prior value of the exception property will be preserved and the property +// will be set to nil. After the callback has completed the new value of the +// exception property will be read, and prior value restored. If the new value +// of exception is not nil, the callback will result in that value being thrown. +// This property may also be used to check for uncaught exceptions arising from +// API function calls (since the default behaviour of "exceptionHandler" is to +// assign an uncaught exception to this property). +// If a JSValue originating from a different JSVirtualMachine than this context +// is assigned to this property, an Objective-C exception will be raised. +@property(retain) JSValue *exception; + +// If a call to an API function results in an uncaught JavaScript exception, the +// "exceptionHandler" block will be invoked. The default implementation for the +// exception handler will store the exception to the exception property on +// context. As a consequence the default behaviour is for unhandled exceptions +// occurring within a callback from JavaScript to be rethrown upon return. +// Setting this value to nil will result in all uncaught exceptions thrown from +// the API being silently consumed. +@property(copy) void(^exceptionHandler)(JSContext *context, JSValue *exception); + +// All instances of JSContext are associated with a single JSVirtualMachine. The +// virtual machine provides an "object space" or set of execution resources. +@property(readonly, retain) JSVirtualMachine *virtualMachine; + +@end + +// Instances of JSContext implement the following methods in order to enable +// support for subscript access by key and index, for example: +// +// JSContext *context; +// JSValue *v = context[@"X"]; // Get value for "X" from the global object. +// context[@"Y"] = v; // Assign 'v' to "Y" on the global object. +// +// An object key passed as a subscript will be converted to a JavaScript value, +// and then the value converted to a string used to resolve a property of the +// global object. +@interface JSContext(SubscriptSupport) + +- (JSValue *)objectForKeyedSubscript:(id)key; +- (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying> *)key; + +@end + +// These functions are for bridging between the C API and the Objective-C API. +@interface JSContext(JSContextRefSupport) +// Creates a JSContext, wrapping its C API counterpart. ++ (JSContext *)contextWithJSGlobalContextRef:(JSGlobalContextRef)jsGlobalContextRef; +// Returns the C API counterpart wrapped by a JSContext. +- (JSGlobalContextRef)JSGlobalContextRef; +@end + +#endif + +#endif // JSContext_h diff --git a/Source/JavaScriptCore/API/JSContext.mm b/Source/JavaScriptCore/API/JSContext.mm new file mode 100644 index 000000000..58754b38c --- /dev/null +++ b/Source/JavaScriptCore/API/JSContext.mm @@ -0,0 +1,289 @@ +/* + * 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. + */ + +#include "config.h" + +#import "APICast.h" +#import "APIShims.h" +#import "JSContextInternal.h" +#import "JSGlobalObject.h" +#import "JSValueInternal.h" +#import "JSVirtualMachineInternal.h" +#import "JSWrapperMap.h" +#import "JavaScriptCore.h" +#import "ObjcRuntimeExtras.h" +#import "Operations.h" +#import "StrongInlines.h" +#import <wtf/HashSet.h> + +#if JSC_OBJC_API_ENABLED + +@implementation JSContext { + JSVirtualMachine *m_virtualMachine; + JSGlobalContextRef m_context; + JSWrapperMap *m_wrapperMap; + JSC::Strong<JSC::JSObject> m_exception; +} + +@synthesize exceptionHandler; + +- (JSGlobalContextRef)JSGlobalContextRef +{ + return m_context; +} + +- (id)init +{ + return [self initWithVirtualMachine:[[[JSVirtualMachine alloc] init] autorelease]]; +} + +- (id)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine +{ + self = [super init]; + if (!self) + return nil; + + m_virtualMachine = [virtualMachine retain]; + m_context = JSGlobalContextCreateInGroup(getGroupFromVirtualMachine(virtualMachine), 0); + m_wrapperMap = [[JSWrapperMap alloc] initWithContext:self]; + + self.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) { + context.exception = exceptionValue; + }; + + [m_virtualMachine addContext:self forGlobalContextRef:m_context]; + + return self; +} + +- (void)dealloc +{ + [m_wrapperMap release]; + JSGlobalContextRelease(m_context); + [m_virtualMachine release]; + [self.exceptionHandler release]; + [super dealloc]; +} + +- (JSValue *)evaluateScript:(NSString *)script +{ + JSValueRef exceptionValue = 0; + JSStringRef scriptJS = JSStringCreateWithCFString((CFStringRef)script); + JSValueRef result = JSEvaluateScript(m_context, scriptJS, 0, 0, 0, &exceptionValue); + JSStringRelease(scriptJS); + + if (exceptionValue) + return [self valueFromNotifyException:exceptionValue]; + + return [JSValue valueWithJSValueRef:result inContext:self]; +} + +- (void)setException:(JSValue *)value +{ + if (value) + m_exception.set(toJS(m_context)->vm(), toJS(JSValueToObject(m_context, valueInternalValue(value), 0))); + else + m_exception.clear(); +} + +- (JSValue *)exception +{ + if (!m_exception) + return nil; + return [JSValue valueWithJSValueRef:toRef(m_exception.get()) inContext:self]; +} + +- (JSWrapperMap *)wrapperMap +{ + return m_wrapperMap; +} + +- (JSValue *)globalObject +{ + return [JSValue valueWithJSValueRef:JSContextGetGlobalObject(m_context) inContext:self]; +} + ++ (JSContext *)currentContext +{ + WTFThreadData& threadData = wtfThreadData(); + CallbackData *entry = (CallbackData *)threadData.m_apiData; + return entry ? entry->context : nil; +} + ++ (JSValue *)currentThis +{ + WTFThreadData& threadData = wtfThreadData(); + CallbackData *entry = (CallbackData *)threadData.m_apiData; + + if (!entry->currentThis) + entry->currentThis = [[JSValue alloc] initWithValue:entry->thisValue inContext:[JSContext currentContext]]; + + return entry->currentThis; +} + ++ (NSArray *)currentArguments +{ + WTFThreadData& threadData = wtfThreadData(); + CallbackData *entry = (CallbackData *)threadData.m_apiData; + + if (!entry->currentArguments) { + JSContext *context = [JSContext currentContext]; + size_t count = entry->argumentCount; + JSValue * argumentArray[count]; + for (size_t i =0; i < count; ++i) + argumentArray[i] = [JSValue valueWithJSValueRef:entry->arguments[i] inContext:context]; + entry->currentArguments = [[NSArray alloc] initWithObjects:argumentArray count:count]; + } + + return entry->currentArguments; +} + +- (JSVirtualMachine *)virtualMachine +{ + return m_virtualMachine; +} + +@end + +@implementation JSContext(SubscriptSupport) + +- (JSValue *)objectForKeyedSubscript:(id)key +{ + return [self globalObject][key]; +} + +- (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying> *)key +{ + [self globalObject][key] = object; +} + +@end + +@implementation JSContext(Internal) + +- (id)initWithGlobalContextRef:(JSGlobalContextRef)context +{ + self = [super init]; + if (!self) + return nil; + + JSC::JSGlobalObject* globalObject = toJS(context)->lexicalGlobalObject(); + m_virtualMachine = [[JSVirtualMachine virtualMachineWithContextGroupRef:toRef(&globalObject->vm())] retain]; + ASSERT(m_virtualMachine); + m_context = JSGlobalContextRetain(context); + m_wrapperMap = [[JSWrapperMap alloc] initWithContext:self]; + + self.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) { + context.exception = exceptionValue; + }; + + [m_virtualMachine addContext:self forGlobalContextRef:m_context]; + + return self; +} + +- (void)notifyException:(JSValueRef)exceptionValue +{ + self.exceptionHandler(self, [JSValue valueWithJSValueRef:exceptionValue inContext:self]); +} + +- (JSValue *)valueFromNotifyException:(JSValueRef)exceptionValue +{ + [self notifyException:exceptionValue]; + return [JSValue valueWithUndefinedInContext:self]; +} + +- (BOOL)boolFromNotifyException:(JSValueRef)exceptionValue +{ + [self notifyException:exceptionValue]; + return NO; +} + +- (void)beginCallbackWithData:(CallbackData *)callbackData thisValue:(JSValueRef)thisValue argumentCount:(size_t)argumentCount arguments:(const JSValueRef *)arguments +{ + WTFThreadData& threadData = wtfThreadData(); + [self retain]; + CallbackData *prevStack = (CallbackData *)threadData.m_apiData; + *callbackData = (CallbackData){ prevStack, self, [self.exception retain], thisValue, nil, argumentCount, arguments, nil }; + threadData.m_apiData = callbackData; + self.exception = nil; +} + +- (void)endCallbackWithData:(CallbackData *)callbackData +{ + WTFThreadData& threadData = wtfThreadData(); + self.exception = callbackData->preservedException; + [callbackData->preservedException release]; + [callbackData->currentThis release]; + [callbackData->currentArguments release]; + threadData.m_apiData = callbackData->next; + [self release]; +} + +- (JSValue *)wrapperForObjCObject:(id)object +{ + // Lock access to m_wrapperMap + JSC::JSLockHolder lock(toJS(m_context)); + return [m_wrapperMap jsWrapperForObject:object]; +} + +- (JSValue *)wrapperForJSObject:(JSValueRef)value +{ + JSC::JSLockHolder lock(toJS(m_context)); + return [m_wrapperMap objcWrapperForJSValueRef:value]; +} + ++ (JSContext *)contextWithJSGlobalContextRef:(JSGlobalContextRef)globalContext +{ + JSVirtualMachine *virtualMachine = [JSVirtualMachine virtualMachineWithContextGroupRef:toRef(&toJS(globalContext)->vm())]; + JSContext *context = [virtualMachine contextForGlobalContextRef:globalContext]; + if (!context) + context = [[[JSContext alloc] initWithGlobalContextRef:globalContext] autorelease]; + return context; +} + +@end + +WeakContextRef::WeakContextRef(JSContext *context) +{ + objc_initWeak(&m_weakContext, context); +} + +WeakContextRef::~WeakContextRef() +{ + objc_destroyWeak(&m_weakContext); +} + +JSContext * WeakContextRef::get() +{ + return objc_loadWeak(&m_weakContext); +} + +void WeakContextRef::set(JSContext *context) +{ + objc_storeWeak(&m_weakContext, context); +} + +#endif diff --git a/Source/JavaScriptCore/API/JSContextInternal.h b/Source/JavaScriptCore/API/JSContextInternal.h new file mode 100644 index 000000000..d08e97d93 --- /dev/null +++ b/Source/JavaScriptCore/API/JSContextInternal.h @@ -0,0 +1,80 @@ +/* + * 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 JSContextInternal_h +#define JSContextInternal_h + +#import <JavaScriptCore/JavaScriptCore.h> + +#if JSC_OBJC_API_ENABLED + +#import <JavaScriptCore/JSContext.h> + +struct CallbackData { + CallbackData *next; + JSContext *context; + JSValue *preservedException; + JSValueRef thisValue; + JSValue *currentThis; + size_t argumentCount; + const JSValueRef *arguments; + NSArray *currentArguments; +}; + +class WeakContextRef { +public: + WeakContextRef(JSContext * = nil); + ~WeakContextRef(); + + JSContext * get(); + void set(JSContext *); + +private: + JSContext *m_weakContext; +}; + +@class JSWrapperMap; + +@interface JSContext(Internal) + +- (id)initWithGlobalContextRef:(JSGlobalContextRef)context; + +- (void)notifyException:(JSValueRef)exception; +- (JSValue *)valueFromNotifyException:(JSValueRef)exception; +- (BOOL)boolFromNotifyException:(JSValueRef)exception; + +- (void)beginCallbackWithData:(CallbackData *)callbackData thisValue:(JSValueRef)thisValue argumentCount:(size_t)argumentCount arguments:(const JSValueRef *)arguments; +- (void)endCallbackWithData:(CallbackData *)callbackData; + +- (JSValue *)wrapperForObjCObject:(id)object; +- (JSValue *)wrapperForJSObject:(JSValueRef)value; + +@property (readonly, retain) JSWrapperMap *wrapperMap; + +@end + +#endif + +#endif // JSContextInternal_h diff --git a/Source/JavaScriptCore/API/JSContextRef.cpp b/Source/JavaScriptCore/API/JSContextRef.cpp index 162c825aa..3869e87bf 100644 --- a/Source/JavaScriptCore/API/JSContextRef.cpp +++ b/Source/JavaScriptCore/API/JSContextRef.cpp @@ -35,6 +35,8 @@ #include "JSClassRef.h" #include "JSGlobalObject.h" #include "JSObject.h" +#include "Operations.h" +#include "SourceProvider.h" #include <wtf/text/StringBuilder.h> #include <wtf/text/StringHash.h> @@ -54,7 +56,7 @@ using namespace JSC; JSContextGroupRef JSContextGroupCreate() { initializeThreading(); - return toRef(JSGlobalData::createContextGroup().leakRef()); + return toRef(VM::createContextGroup().leakRef()); } JSContextGroupRef JSContextGroupRetain(JSContextGroupRef group) @@ -65,7 +67,44 @@ JSContextGroupRef JSContextGroupRetain(JSContextGroupRef group) void JSContextGroupRelease(JSContextGroupRef group) { - toJS(group)->deref(); + IdentifierTable* savedIdentifierTable; + VM& vm = *toJS(group); + + { + JSLockHolder lock(vm); + savedIdentifierTable = wtfThreadData().setCurrentIdentifierTable(vm.identifierTable); + vm.deref(); + } + + wtfThreadData().setCurrentIdentifierTable(savedIdentifierTable); +} + +static bool internalScriptTimeoutCallback(ExecState* exec, void* callbackPtr, void* callbackData) +{ + JSShouldTerminateCallback callback = reinterpret_cast<JSShouldTerminateCallback>(callbackPtr); + JSContextRef contextRef = toRef(exec); + ASSERT(callback); + return callback(contextRef, callbackData); +} + +void JSContextGroupSetExecutionTimeLimit(JSContextGroupRef group, double limit, JSShouldTerminateCallback callback, void* callbackData) +{ + VM& vm = *toJS(group); + APIEntryShim entryShim(&vm); + Watchdog& watchdog = vm.watchdog; + if (callback) { + void* callbackPtr = reinterpret_cast<void*>(callback); + watchdog.setTimeLimit(vm, limit, internalScriptTimeoutCallback, callbackPtr, callbackData); + } else + watchdog.setTimeLimit(vm, limit); +} + +void JSContextGroupClearExecutionTimeLimit(JSContextGroupRef group) +{ + VM& vm = *toJS(group); + APIEntryShim entryShim(&vm); + Watchdog& watchdog = vm.watchdog; + watchdog.setTimeLimit(vm, std::numeric_limits<double>::infinity()); } // From the API's perspective, a global context remains alive iff it has been JSGlobalContextRetained. @@ -75,10 +114,10 @@ JSGlobalContextRef JSGlobalContextCreate(JSClassRef globalObjectClass) initializeThreading(); #if OS(DARWIN) - // If the application was linked before JSGlobalContextCreate was changed to use a unique JSGlobalData, + // If the application was linked before JSGlobalContextCreate was changed to use a unique VM, // we use a shared one for backwards compatibility. if (NSVersionOfLinkTimeLibrary("JavaScriptCore") <= webkitFirstVersionWithConcurrentGlobalContexts) { - return JSGlobalContextCreateInGroup(toRef(&JSGlobalData::sharedInstance()), globalObjectClass); + return JSGlobalContextCreateInGroup(toRef(&VM::sharedInstance()), globalObjectClass); } #endif // OS(DARWIN) @@ -89,22 +128,22 @@ JSGlobalContextRef JSGlobalContextCreateInGroup(JSContextGroupRef group, JSClass { initializeThreading(); - RefPtr<JSGlobalData> globalData = group ? PassRefPtr<JSGlobalData>(toJS(group)) : JSGlobalData::createContextGroup(); + RefPtr<VM> vm = group ? PassRefPtr<VM>(toJS(group)) : VM::createContextGroup(); - APIEntryShim entryShim(globalData.get(), false); - globalData->makeUsableFromMultipleThreads(); + APIEntryShim entryShim(vm.get(), false); + vm->makeUsableFromMultipleThreads(); if (!globalObjectClass) { - JSGlobalObject* globalObject = JSGlobalObject::create(*globalData, JSGlobalObject::createStructure(*globalData, jsNull())); + JSGlobalObject* globalObject = JSGlobalObject::create(*vm, JSGlobalObject::createStructure(*vm, jsNull())); return JSGlobalContextRetain(toGlobalRef(globalObject->globalExec())); } - JSGlobalObject* globalObject = JSCallbackObject<JSGlobalObject>::create(*globalData, globalObjectClass, JSCallbackObject<JSGlobalObject>::createStructure(*globalData, 0, jsNull())); + JSGlobalObject* globalObject = JSCallbackObject<JSGlobalObject>::create(*vm, globalObjectClass, JSCallbackObject<JSGlobalObject>::createStructure(*vm, 0, jsNull())); ExecState* exec = globalObject->globalExec(); JSValue prototype = globalObjectClass->prototype(exec); if (!prototype) prototype = jsNull(); - globalObject->resetPrototype(*globalData, prototype); + globalObject->resetPrototype(*vm, prototype); return JSGlobalContextRetain(toGlobalRef(exec)); } @@ -113,9 +152,9 @@ JSGlobalContextRef JSGlobalContextRetain(JSGlobalContextRef ctx) ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); - JSGlobalData& globalData = exec->globalData(); + VM& vm = exec->vm(); gcProtect(exec->dynamicGlobalObject()); - globalData.ref(); + vm.ref(); return ctx; } @@ -126,13 +165,13 @@ void JSGlobalContextRelease(JSGlobalContextRef ctx) { JSLockHolder lock(exec); - JSGlobalData& globalData = exec->globalData(); - savedIdentifierTable = wtfThreadData().setCurrentIdentifierTable(globalData.identifierTable); + VM& vm = exec->vm(); + savedIdentifierTable = wtfThreadData().setCurrentIdentifierTable(vm.identifierTable); bool protectCountIsZero = Heap::heap(exec->dynamicGlobalObject())->unprotect(exec->dynamicGlobalObject()); if (protectCountIsZero) - globalData.heap.reportAbandonedObjectGraph(); - globalData.deref(); + vm.heap.reportAbandonedObjectGraph(); + vm.deref(); } wtfThreadData().setCurrentIdentifierTable(savedIdentifierTable); @@ -140,6 +179,10 @@ void JSGlobalContextRelease(JSGlobalContextRef ctx) JSObjectRef JSContextGetGlobalObject(JSContextRef ctx) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -149,12 +192,20 @@ JSObjectRef JSContextGetGlobalObject(JSContextRef ctx) JSContextGroupRef JSContextGetGroup(JSContextRef ctx) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); - return toRef(&exec->globalData()); + return toRef(&exec->vm()); } JSGlobalContextRef JSContextGetGlobalContext(JSContextRef ctx) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -163,53 +214,46 @@ JSGlobalContextRef JSContextGetGlobalContext(JSContextRef ctx) JSStringRef JSContextCreateBacktrace(JSContextRef ctx, unsigned maxStackSize) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); JSLockHolder lock(exec); - - unsigned count = 0; StringBuilder builder; - CallFrame* callFrame = exec; - String functionName; - if (exec->callee()) { - if (asObject(exec->callee())->inherits(&InternalFunction::s_info)) { - functionName = asInternalFunction(exec->callee())->name(exec); - builder.appendLiteral("#0 "); - builder.append(functionName); - builder.appendLiteral("() "); - count++; - } - } - while (true) { - ASSERT(callFrame); - int signedLineNumber; - intptr_t sourceID; - String urlString; - JSValue function; + Vector<StackFrame> stackTrace; + Interpreter::getStackTrace(&exec->vm(), stackTrace, maxStackSize); - exec->interpreter()->retrieveLastCaller(callFrame, signedLineNumber, sourceID, urlString, function); - - if (function) - functionName = jsCast<JSFunction*>(function)->name(exec); + for (size_t i = 0; i < stackTrace.size(); i++) { + String urlString; + String functionName; + StackFrame& frame = stackTrace[i]; + JSValue function = frame.callee.get(); + if (frame.callee) + functionName = frame.friendlyFunctionName(exec); else { // Caller is unknown, but if frame is empty we should still add the frame, because // something called us, and gave us arguments. - if (count) + if (i) break; } - unsigned lineNumber = signedLineNumber >= 0 ? signedLineNumber : 0; + unsigned lineNumber; + unsigned column; + frame.computeLineAndColumn(lineNumber, column); if (!builder.isEmpty()) builder.append('\n'); builder.append('#'); - builder.appendNumber(count); + builder.appendNumber(i); builder.append(' '); builder.append(functionName); builder.appendLiteral("() at "); builder.append(urlString); - builder.append(':'); - builder.appendNumber(lineNumber); - if (!function || ++count == maxStackSize) + if (frame.codeType != StackFrameNativeCode) { + builder.append(':'); + builder.appendNumber(lineNumber); + } + if (!function) break; - callFrame = callFrame->callerFrame(); } return OpaqueJSString::create(builder.toString()).leakRef(); } diff --git a/Source/JavaScriptCore/API/JSContextRefPrivate.h b/Source/JavaScriptCore/API/JSContextRefPrivate.h index 4f77aeada..8d7684ac0 100644 --- a/Source/JavaScriptCore/API/JSContextRefPrivate.h +++ b/Source/JavaScriptCore/API/JSContextRefPrivate.h @@ -55,6 +55,54 @@ JS_EXPORT JSGlobalContextRef JSContextGetGlobalContext(JSContextRef ctx); */ JS_EXPORT JSStringRef JSContextCreateBacktrace(JSContextRef ctx, unsigned maxStackSize) AVAILABLE_IN_WEBKIT_VERSION_4_0; + +/*! +@typedef JSShouldTerminateCallback +@abstract The callback invoked when script execution has exceeded the allowed + time limit previously specified via JSContextGroupSetExecutionTimeLimit. +@param ctx The execution context to use. +@param context User specified context data previously passed to + JSContextGroupSetExecutionTimeLimit. +@discussion If you named your function Callback, you would declare it like this: + + bool Callback(JSContextRef ctx, void* context); + + If you return true, the timed out script will terminate. + If you return false, the script will run for another period of the allowed + time limit specified via JSContextGroupSetExecutionTimeLimit. + + Within this callback function, you may call JSContextGroupSetExecutionTimeLimit + to set a new time limit, or JSContextGroupClearExecutionTimeLimit to cancel the + timeout. +*/ +typedef bool +(*JSShouldTerminateCallback) (JSContextRef ctx, void* context); + +/*! +@function +@abstract Sets the script execution time limit. +@param group The JavaScript context group that this time limit applies to. +@param limit The time limit of allowed script execution time in seconds. +@param callback The callback function that will be invoked when the time limit + has been reached. This will give you a chance to decide if you want to + terminate the script or not. If you pass a NULL callback, the script will be + terminated unconditionally when the time limit has been reached. +@param context User data that you can provide to be passed back to you + in your callback. + + In order to guarantee that the execution time limit will take effect, you will + need to call JSContextGroupSetExecutionTimeLimit before you start executing + any scripts. +*/ +JS_EXPORT void JSContextGroupSetExecutionTimeLimit(JSContextGroupRef, double limit, JSShouldTerminateCallback, void* context) AVAILABLE_IN_WEBKIT_VERSION_4_0; + +/*! +@function +@abstract Clears the script execution time limit. +@param group The JavaScript context group that the time limit is cleared on. +*/ +JS_EXPORT void JSContextGroupClearExecutionTimeLimit(JSContextGroupRef) AVAILABLE_IN_WEBKIT_VERSION_4_0; + #ifdef __cplusplus } #endif diff --git a/Source/JavaScriptCore/API/JSExport.h b/Source/JavaScriptCore/API/JSExport.h new file mode 100644 index 000000000..96e9fec35 --- /dev/null +++ b/Source/JavaScriptCore/API/JSExport.h @@ -0,0 +1,130 @@ +/* + * 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. + */ + +#import <JavaScriptCore/JavaScriptCore.h> + +#if JSC_OBJC_API_ENABLED + +// When a JavaScript value is created from an instance of an Objective-C class +// for which no copying conversion is specified a JavaScript wrapper object will +// be created. +// +// In JavaScript inheritance is supported via a chain of prototype objects, and +// for each Objective-C class (and per JSContext) an object appropriate for use +// as a prototype will be provided. For the class NSObject the prototype object +// will be the JavaScript context's Object Prototype. For all other Objective-C +// classes a Prototype object will be created. The Prototype object for a given +// Objective-C class will have its internal [Prototype] property set to point to +// the Prototype object of the Objective-C class's superclass. As such the +// prototype chain for a JavaScript wrapper object will reflect the wrapped +// Objective-C type's inheritance hierarchy. +// +// In addition to the Prototype object a JavaScript Constructor object will also +// be produced for each Objective-C class. The Constructor object has a property +// named 'prototype' that references the Prototype object, and the Prototype +// object has a property named 'constructor' that references the Constructor. +// The Constructor object is not callable. +// +// By default no methods or properties of the Objective-C class will be exposed +// to JavaScript, however methods and properties may explicitly be exported. +// For each protocol that a class conforms to, if the protocol incorporates the +// protocol JSExport, then the protocol will be interpreted as a list of methods +// and properties to be exported to JavaScript. +// +// For each instance method being exported, a corresponding JavaScript function +// will be assigned as a property of the Prototype object, for each Objective-C +// property being exported a JavaScript accessor property will be created on the +// Prototype, and for each class method exported a JavaScript function will be +// created on the Constructor object. For example: +// +// @protocol MyClassJavaScriptMethods <JSExport> +// - (void)foo; +// @end +// +// @interface MyClass : NSObject <MyClassJavaScriptMethods> +// - (void)foo; +// - (void)bar; +// @end +// +// Data properties that are created on the prototype or constructor objects have +// the attributes: writable:true, enumerable:false, configurable:true. Accessor +// properties have the attributes: enumerable:false and configurable:true. +// +// If an instance of MyClass is converted to a JavaScript value, the resulting +// wrapper object will (via its prototype) export the method "foo" to JavaScript, +// since the class conforms to the MyClassJavaScriptMethods protocol, and this +// protocol incorporates JSExport. "bar" will not be exported. +// +// Properties, arguments, and return values of the following types are +// supported: +// +// Primitive numbers: signed values of up to 32-bits are converted in a manner +// consistent with valueWithInt32/toInt32, unsigned values of up to 32-bits +// are converted in a manner consistent with valueWithUInt32/toUInt32, all +// other numeric values are converted consistently with valueWithDouble/ +// toDouble. +// BOOL: values are converted consistently with valueWithBool/toBool. +// id: values are converted consistently with valueWithObject/toObject. +// <Objective-C Class>: - where the type is a pointer to a specified Objective-C +// class, conversion is consistent with valueWithObjectOfClass/toObject. +// struct types: C struct types are supported, where JSValue provides support +// for the given type. Support is built in for CGPoint, NSRange, CGRect, and +// CGSize. +// block types: In addition to support provided by valueWithObject/toObject for +// block types, if a JavaScript Function is passed as an argument, where the +// type required is a block with a void return value (and where the block's +// arguments are all of supported types), then a special adaptor block +// will be created, allowing the JavaScript function to be used in the place +// of a block. +// +// For any interface that conforms to JSExport the normal copying conversion for +// built in types will be inhibited - so, for example, if an instance that +// derives from NSString but conforms to JSExport is passed to valueWithObject: +// then a wrapper object for the Objective-C object will be returned rather than +// a JavaScript string primitive. +@protocol JSExport +@end + +// When a selector that takes one or more arguments is converted to a JavaScript +// property name, by default a property name will be generated by performing the +// following conversion: +// - All colons are removed from the selector +// - Any lowercase letter that had followed a colon will be capitalized. +// Under the default conversion a selector "doFoo:withBar:" will be exported as +// "doFooWithBar". The default conversion may be overriden using the JSExportAs +// macro, for example to export a method "doFoo:withBar:" as "doFoo": +// +// @protocol MyClassJavaScriptMethods <JSExport> +// JSExportAs(doFoo, +// - (void)doFoo:(id)foo withBar:(id)bar +// ); +// @end +// +// Note that the JSExport macro may only be applied to a selector that takes one +// or more argument. +#define JSExportAs(PropertyName, Selector) \ + @optional Selector __JS_EXPORT_AS__##PropertyName:(id)argument; @required Selector + +#endif diff --git a/Source/JavaScriptCore/API/JSManagedValue.h b/Source/JavaScriptCore/API/JSManagedValue.h new file mode 100644 index 000000000..5ff3f83cb --- /dev/null +++ b/Source/JavaScriptCore/API/JSManagedValue.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2013 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSManagedValue_h +#define JSManagedValue_h + +#import <JavaScriptCore/JSBase.h> + +#if JSC_OBJC_API_ENABLED + +@class JSValue; +@class JSContext; + +// JSManagedValue represents a "conditionally retained" JSValue. +// "Conditionally retained" means that as long as either the JSManagedValue +// JavaScript value is reachable through the JavaScript object graph +// or the JSManagedValue object is reachable through the external Objective-C +// object graph as reported to the JSVirtualMachine using +// addManagedReference:withOwner:, the corresponding JavaScript value will +// be retained. However, if neither of these conditions are true, the +// corresponding JSValue will be released and set to nil. +// +// The primary use case for JSManagedValue is for safely referencing JSValues +// from the Objective-C heap. It is incorrect to store a JSValue into an +// Objective-C heap object, as this can very easily create a reference cycle, +// keeping the entire JSContext alive. +NS_CLASS_AVAILABLE(10_9, NA) +@interface JSManagedValue : NSObject + +// Convenience method for creating JSManagedValues from JSValues. ++ (JSManagedValue *)managedValueWithValue:(JSValue *)value; + +// Create a JSManagedValue. +- (id)initWithValue:(JSValue *)value; + +// Get the JSValue to which this JSManagedValue refers. If the JavaScript value has been collected, +// this method returns nil. +- (JSValue *)value; + +@end + +#endif // JSC_OBJC_API_ENABLED + +#endif // JSManagedValue_h diff --git a/Source/JavaScriptCore/API/JSManagedValue.mm b/Source/JavaScriptCore/API/JSManagedValue.mm new file mode 100644 index 000000000..f336ba662 --- /dev/null +++ b/Source/JavaScriptCore/API/JSManagedValue.mm @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2013 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#import "config.h" +#import "JSManagedValue.h" + +#if JSC_OBJC_API_ENABLED + +#import "APICast.h" +#import "Heap.h" +#import "JSCJSValueInlines.h" +#import "JSContextInternal.h" +#import "JSValueInternal.h" +#import "Weak.h" +#import "WeakHandleOwner.h" +#import "ObjcRuntimeExtras.h" + +class JSManagedValueHandleOwner : public JSC::WeakHandleOwner { +public: + virtual void finalize(JSC::Handle<JSC::Unknown>, void* context); + virtual bool isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown>, void* context, JSC::SlotVisitor&); +}; + +static JSManagedValueHandleOwner* managedValueHandleOwner() +{ + DEFINE_STATIC_LOCAL(JSManagedValueHandleOwner, jsManagedValueHandleOwner, ()); + return &jsManagedValueHandleOwner; +} + +@implementation JSManagedValue { + JSC::Weak<JSC::JSObject> m_value; +} + ++ (JSManagedValue *)managedValueWithValue:(JSValue *)value +{ + return [[[self alloc] initWithValue:value] autorelease]; +} + +- (id)init +{ + return [self initWithValue:nil]; +} + +- (id)initWithValue:(JSValue *)value +{ + self = [super init]; + 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); + } + + return self; +} + +- (JSValue *)value +{ + if (!m_value) + return nil; + JSC::JSObject* object = m_value.get(); + JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(object->structure()->globalObject()->globalExec())]; + return [JSValue valueWithJSValueRef:toRef(object) inContext:context]; +} + +- (void)disconnectValue +{ + m_value.clear(); +} + +@end + +@interface JSManagedValue (PrivateMethods) +- (void)disconnectValue; +@end + +bool JSManagedValueHandleOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown>, void* context, JSC::SlotVisitor& visitor) +{ + JSManagedValue *managedValue = static_cast<JSManagedValue *>(context); + return visitor.containsOpaqueRoot(managedValue); +} + +void JSManagedValueHandleOwner::finalize(JSC::Handle<JSC::Unknown>, void* context) +{ + JSManagedValue *managedValue = static_cast<JSManagedValue *>(context); + [managedValue disconnectValue]; +} + +#endif // JSC_OBJC_API_ENABLED diff --git a/Source/JavaScriptCore/API/JSObjectRef.cpp b/Source/JavaScriptCore/API/JSObjectRef.cpp index c62efc60d..5ba446513 100644 --- a/Source/JavaScriptCore/API/JSObjectRef.cpp +++ b/Source/JavaScriptCore/API/JSObjectRef.cpp @@ -37,6 +37,7 @@ #include "FunctionConstructor.h" #include "Identifier.h" #include "InitializeThreading.h" +#include "JSAPIWrapperObject.h" #include "JSArray.h" #include "JSCallbackConstructor.h" #include "JSCallbackFunction.h" @@ -48,7 +49,9 @@ #include "JSRetainPtr.h" #include "JSString.h" #include "JSValueRef.h" +#include "ObjectConstructor.h" #include "ObjectPrototype.h" +#include "Operations.h" #include "PropertyNameArray.h" #include "RegExpConstructor.h" @@ -77,6 +80,10 @@ void JSClassRelease(JSClassRef jsClass) JSObjectRef JSObjectMake(JSContextRef ctx, JSClassRef jsClass, void* data) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -85,13 +92,17 @@ JSObjectRef JSObjectMake(JSContextRef ctx, JSClassRef jsClass, void* data) JSCallbackObject<JSDestructibleObject>* object = JSCallbackObject<JSDestructibleObject>::create(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->callbackObjectStructure(), jsClass, data); if (JSObject* prototype = jsClass->prototype(exec)) - object->setPrototype(exec->globalData(), prototype); + object->setPrototype(exec->vm(), prototype); return toRef(object); } JSObjectRef JSObjectMakeFunctionWithCallback(JSContextRef ctx, JSStringRef name, JSObjectCallAsFunctionCallback callAsFunction) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); return toRef(JSCallbackFunction::create(exec, exec->lexicalGlobalObject(), callAsFunction, name ? name->string() : ASCIILiteral("anonymous"))); @@ -99,6 +110,10 @@ JSObjectRef JSObjectMakeFunctionWithCallback(JSContextRef ctx, JSStringRef name, JSObjectRef JSObjectMakeConstructor(JSContextRef ctx, JSClassRef jsClass, JSObjectCallAsConstructorCallback callAsConstructor) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -107,16 +122,20 @@ JSObjectRef JSObjectMakeConstructor(JSContextRef ctx, JSClassRef jsClass, JSObje jsPrototype = exec->lexicalGlobalObject()->objectPrototype(); JSCallbackConstructor* constructor = JSCallbackConstructor::create(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->callbackConstructorStructure(), jsClass, callAsConstructor); - constructor->putDirect(exec->globalData(), exec->propertyNames().prototype, jsPrototype, DontEnum | DontDelete | ReadOnly); + constructor->putDirect(exec->vm(), exec->propertyNames().prototype, jsPrototype, DontEnum | DontDelete | ReadOnly); return toRef(constructor); } JSObjectRef JSObjectMakeFunction(JSContextRef ctx, JSStringRef name, unsigned parameterCount, const JSStringRef parameterNames[], JSStringRef body, JSStringRef sourceURL, int startingLineNumber, JSValueRef* exception) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); - Identifier nameID = name ? name->identifier(&exec->globalData()) : Identifier(exec, "anonymous"); + Identifier nameID = name ? name->identifier(&exec->vm()) : Identifier(exec, "anonymous"); MarkedArgumentBuffer args; for (unsigned i = 0; i < parameterCount; i++) @@ -135,6 +154,10 @@ JSObjectRef JSObjectMakeFunction(JSContextRef ctx, JSStringRef name, unsigned pa JSObjectRef JSObjectMakeArray(JSContextRef ctx, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -160,6 +183,10 @@ JSObjectRef JSObjectMakeArray(JSContextRef ctx, size_t argumentCount, const JSVa JSObjectRef JSObjectMakeDate(JSContextRef ctx, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -180,6 +207,10 @@ JSObjectRef JSObjectMakeDate(JSContextRef ctx, size_t argumentCount, const JSVal JSObjectRef JSObjectMakeError(JSContextRef ctx, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -199,6 +230,10 @@ JSObjectRef JSObjectMakeError(JSContextRef ctx, size_t argumentCount, const JSVa JSObjectRef JSObjectMakeRegExp(JSContextRef ctx, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -219,6 +254,10 @@ JSObjectRef JSObjectMakeRegExp(JSContextRef ctx, size_t argumentCount, const JSV JSValueRef JSObjectGetPrototype(JSContextRef ctx, JSObjectRef object) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -228,33 +267,45 @@ JSValueRef JSObjectGetPrototype(JSContextRef ctx, JSObjectRef object) void JSObjectSetPrototype(JSContextRef ctx, JSObjectRef object, JSValueRef value) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); JSObject* jsObject = toJS(object); JSValue jsValue = toJS(exec, value); - jsObject->setPrototypeWithCycleCheck(exec->globalData(), jsValue.isObject() ? jsValue : jsNull()); + jsObject->setPrototypeWithCycleCheck(exec->vm(), jsValue.isObject() ? jsValue : jsNull()); } bool JSObjectHasProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return false; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); JSObject* jsObject = toJS(object); - return jsObject->hasProperty(exec, propertyName->identifier(&exec->globalData())); + return jsObject->hasProperty(exec, propertyName->identifier(&exec->vm())); } JSValueRef JSObjectGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); JSObject* jsObject = toJS(object); - JSValue jsValue = jsObject->get(exec, propertyName->identifier(&exec->globalData())); + JSValue jsValue = jsObject->get(exec, propertyName->identifier(&exec->vm())); if (exec->hadException()) { if (exception) *exception = toRef(exec, exec->exception()); @@ -265,11 +316,15 @@ JSValueRef JSObjectGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef void JSObjectSetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSPropertyAttributes attributes, JSValueRef* exception) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); JSObject* jsObject = toJS(object); - Identifier name(propertyName->identifier(&exec->globalData())); + Identifier name(propertyName->identifier(&exec->vm())); JSValue jsValue = toJS(exec, value); if (attributes && !jsObject->hasProperty(exec, name)) @@ -288,6 +343,10 @@ void JSObjectSetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef prope JSValueRef JSObjectGetPropertyAtIndex(JSContextRef ctx, JSObjectRef object, unsigned propertyIndex, JSValueRef* exception) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -305,6 +364,10 @@ JSValueRef JSObjectGetPropertyAtIndex(JSContextRef ctx, JSObjectRef object, unsi void JSObjectSetPropertyAtIndex(JSContextRef ctx, JSObjectRef object, unsigned propertyIndex, JSValueRef value, JSValueRef* exception) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -321,12 +384,16 @@ void JSObjectSetPropertyAtIndex(JSContextRef ctx, JSObjectRef object, unsigned p bool JSObjectDeleteProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return false; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); JSObject* jsObject = toJS(object); - bool result = jsObject->methodTable()->deleteProperty(jsObject, exec, propertyName->identifier(&exec->globalData())); + bool result = jsObject->methodTable()->deleteProperty(jsObject, exec, propertyName->identifier(&exec->vm())); if (exec->hadException()) { if (exception) *exception = toRef(exec, exec->exception()); @@ -337,19 +404,23 @@ bool JSObjectDeleteProperty(JSContextRef ctx, JSObjectRef object, JSStringRef pr void* JSObjectGetPrivate(JSObjectRef object) { - JSObject* jsObject = toJS(object); + JSObject* jsObject = uncheckedToJS(object); if (jsObject->inherits(&JSCallbackObject<JSGlobalObject>::s_info)) return jsCast<JSCallbackObject<JSGlobalObject>*>(jsObject)->getPrivate(); if (jsObject->inherits(&JSCallbackObject<JSDestructibleObject>::s_info)) return jsCast<JSCallbackObject<JSDestructibleObject>*>(jsObject)->getPrivate(); +#if JSC_OBJC_API_ENABLED + if (jsObject->inherits(&JSCallbackObject<JSAPIWrapperObject>::s_info)) + return jsCast<JSCallbackObject<JSAPIWrapperObject>*>(jsObject)->getPrivate(); +#endif return 0; } bool JSObjectSetPrivate(JSObjectRef object, void* data) { - JSObject* jsObject = toJS(object); + JSObject* jsObject = uncheckedToJS(object); if (jsObject->inherits(&JSCallbackObject<JSGlobalObject>::s_info)) { jsCast<JSCallbackObject<JSGlobalObject>*>(jsObject)->setPrivate(data); @@ -359,6 +430,12 @@ bool JSObjectSetPrivate(JSObjectRef object, void* data) jsCast<JSCallbackObject<JSDestructibleObject>*>(jsObject)->setPrivate(data); return true; } +#if JSC_OBJC_API_ENABLED + if (jsObject->inherits(&JSCallbackObject<JSAPIWrapperObject>::s_info)) { + jsCast<JSCallbackObject<JSAPIWrapperObject>*>(jsObject)->setPrivate(data); + return true; + } +#endif return false; } @@ -369,11 +446,15 @@ JSValueRef JSObjectGetPrivateProperty(JSContextRef ctx, JSObjectRef object, JSSt APIEntryShim entryShim(exec); JSObject* jsObject = toJS(object); JSValue result; - Identifier name(propertyName->identifier(&exec->globalData())); + Identifier name(propertyName->identifier(&exec->vm())); if (jsObject->inherits(&JSCallbackObject<JSGlobalObject>::s_info)) result = jsCast<JSCallbackObject<JSGlobalObject>*>(jsObject)->getPrivateProperty(name); else if (jsObject->inherits(&JSCallbackObject<JSDestructibleObject>::s_info)) result = jsCast<JSCallbackObject<JSDestructibleObject>*>(jsObject)->getPrivateProperty(name); +#if JSC_OBJC_API_ENABLED + else if (jsObject->inherits(&JSCallbackObject<JSAPIWrapperObject>::s_info)) + result = jsCast<JSCallbackObject<JSAPIWrapperObject>*>(jsObject)->getPrivateProperty(name); +#endif return toRef(exec, result); } @@ -383,15 +464,21 @@ bool JSObjectSetPrivateProperty(JSContextRef ctx, JSObjectRef object, JSStringRe APIEntryShim entryShim(exec); JSObject* jsObject = toJS(object); JSValue jsValue = value ? toJS(exec, value) : JSValue(); - Identifier name(propertyName->identifier(&exec->globalData())); + Identifier name(propertyName->identifier(&exec->vm())); if (jsObject->inherits(&JSCallbackObject<JSGlobalObject>::s_info)) { - jsCast<JSCallbackObject<JSGlobalObject>*>(jsObject)->setPrivateProperty(exec->globalData(), name, jsValue); + jsCast<JSCallbackObject<JSGlobalObject>*>(jsObject)->setPrivateProperty(exec->vm(), name, jsValue); return true; } if (jsObject->inherits(&JSCallbackObject<JSDestructibleObject>::s_info)) { - jsCast<JSCallbackObject<JSDestructibleObject>*>(jsObject)->setPrivateProperty(exec->globalData(), name, jsValue); + jsCast<JSCallbackObject<JSDestructibleObject>*>(jsObject)->setPrivateProperty(exec->vm(), name, jsValue); return true; } +#if JSC_OBJC_API_ENABLED + if (jsObject->inherits(&JSCallbackObject<JSAPIWrapperObject>::s_info)) { + jsCast<JSCallbackObject<JSAPIWrapperObject>*>(jsObject)->setPrivateProperty(exec->vm(), name, jsValue); + return true; + } +#endif return false; } @@ -400,7 +487,7 @@ bool JSObjectDeletePrivateProperty(JSContextRef ctx, JSObjectRef object, JSStrin ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); JSObject* jsObject = toJS(object); - Identifier name(propertyName->identifier(&exec->globalData())); + Identifier name(propertyName->identifier(&exec->vm())); if (jsObject->inherits(&JSCallbackObject<JSGlobalObject>::s_info)) { jsCast<JSCallbackObject<JSGlobalObject>*>(jsObject)->deletePrivateProperty(name); return true; @@ -409,11 +496,19 @@ bool JSObjectDeletePrivateProperty(JSContextRef ctx, JSObjectRef object, JSStrin jsCast<JSCallbackObject<JSDestructibleObject>*>(jsObject)->deletePrivateProperty(name); return true; } +#if JSC_OBJC_API_ENABLED + if (jsObject->inherits(&JSCallbackObject<JSAPIWrapperObject>::s_info)) { + jsCast<JSCallbackObject<JSAPIWrapperObject>*>(jsObject)->deletePrivateProperty(name); + return true; + } +#endif return false; } bool JSObjectIsFunction(JSContextRef, JSObjectRef object) { + if (!object) + return false; CallData callData; JSCell* cell = toJS(object); return cell->methodTable()->getCallData(cell, callData) != CallTypeNone; @@ -424,6 +519,9 @@ JSValueRef JSObjectCallAsFunction(JSContextRef ctx, JSObjectRef object, JSObject ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); + if (!object) + return 0; + JSObject* jsObject = toJS(object); JSObject* jsThisObject = toJS(thisObject); @@ -453,6 +551,8 @@ JSValueRef JSObjectCallAsFunction(JSContextRef ctx, JSObjectRef object, JSObject bool JSObjectIsConstructor(JSContextRef, JSObjectRef object) { + if (!object) + return false; JSObject* jsObject = toJS(object); ConstructData constructData; return jsObject->methodTable()->getConstructData(jsObject, constructData) != ConstructTypeNone; @@ -463,6 +563,9 @@ JSObjectRef JSObjectCallAsConstructor(JSContextRef ctx, JSObjectRef object, size ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); + if (!object) + return 0; + JSObject* jsObject = toJS(object); ConstructData constructData; @@ -486,33 +589,37 @@ JSObjectRef JSObjectCallAsConstructor(JSContextRef ctx, JSObjectRef object, size struct OpaqueJSPropertyNameArray { WTF_MAKE_FAST_ALLOCATED; public: - OpaqueJSPropertyNameArray(JSGlobalData* globalData) + OpaqueJSPropertyNameArray(VM* vm) : refCount(0) - , globalData(globalData) + , vm(vm) { } unsigned refCount; - JSGlobalData* globalData; + VM* vm; Vector<JSRetainPtr<JSStringRef> > array; }; JSPropertyNameArrayRef JSObjectCopyPropertyNames(JSContextRef ctx, JSObjectRef object) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } JSObject* jsObject = toJS(object); ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); - JSGlobalData* globalData = &exec->globalData(); + VM* vm = &exec->vm(); - JSPropertyNameArrayRef propertyNames = new OpaqueJSPropertyNameArray(globalData); - PropertyNameArray array(globalData); + JSPropertyNameArrayRef propertyNames = new OpaqueJSPropertyNameArray(vm); + PropertyNameArray array(vm); jsObject->methodTable()->getPropertyNames(jsObject, exec, array, ExcludeDontEnumProperties); size_t size = array.size(); propertyNames->array.reserveInitialCapacity(size); for (size_t i = 0; i < size; ++i) - propertyNames->array.append(JSRetainPtr<JSStringRef>(Adopt, OpaqueJSString::create(array[i].string()).leakRef())); + propertyNames->array.uncheckedAppend(JSRetainPtr<JSStringRef>(Adopt, OpaqueJSString::create(array[i].string()).leakRef())); return JSPropertyNameArrayRetain(propertyNames); } @@ -526,7 +633,7 @@ JSPropertyNameArrayRef JSPropertyNameArrayRetain(JSPropertyNameArrayRef array) void JSPropertyNameArrayRelease(JSPropertyNameArrayRef array) { if (--array->refCount == 0) { - APIEntryShim entryShim(array->globalData, false); + APIEntryShim entryShim(array->vm, false); delete array; } } @@ -544,6 +651,6 @@ JSStringRef JSPropertyNameArrayGetNameAtIndex(JSPropertyNameArrayRef array, size void JSPropertyNameAccumulatorAddName(JSPropertyNameAccumulatorRef array, JSStringRef propertyName) { PropertyNameArray* propertyNames = toJS(array); - APIEntryShim entryShim(propertyNames->globalData()); - propertyNames->add(propertyName->identifier(propertyNames->globalData())); + APIEntryShim entryShim(propertyNames->vm()); + propertyNames->add(propertyName->identifier(propertyNames->vm())); } diff --git a/Source/JavaScriptCore/API/JSProfilerPrivate.cpp b/Source/JavaScriptCore/API/JSProfilerPrivate.cpp index c83bc63bd..0405b4b26 100644 --- a/Source/JavaScriptCore/API/JSProfilerPrivate.cpp +++ b/Source/JavaScriptCore/API/JSProfilerPrivate.cpp @@ -27,20 +27,20 @@ #include "JSProfilerPrivate.h" #include "APICast.h" +#include "LegacyProfiler.h" #include "OpaqueJSString.h" -#include "Profiler.h" using namespace JSC; void JSStartProfiling(JSContextRef ctx, JSStringRef title) { - Profiler::profiler()->startProfiling(toJS(ctx), title->string()); + LegacyProfiler::profiler()->startProfiling(toJS(ctx), title->string()); } void JSEndProfiling(JSContextRef ctx, JSStringRef title) { ExecState* exec = toJS(ctx); - Profiler* profiler = Profiler::profiler(); + LegacyProfiler* profiler = LegacyProfiler::profiler(); profiler->stopProfiling(exec, title->string()); } diff --git a/Source/JavaScriptCore/API/JSScriptRef.cpp b/Source/JavaScriptCore/API/JSScriptRef.cpp new file mode 100644 index 000000000..8a5f3caf3 --- /dev/null +++ b/Source/JavaScriptCore/API/JSScriptRef.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2012 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include "APICast.h" +#include "APIShims.h" +#include "Completion.h" +#include "JSBasePrivate.h" +#include "VM.h" +#include "JSScriptRefPrivate.h" +#include "OpaqueJSString.h" +#include "Operations.h" +#include "Parser.h" +#include "SourceCode.h" +#include "SourceProvider.h" + +using namespace JSC; + +struct OpaqueJSScript : public SourceProvider { +public: + static WTF::PassRefPtr<OpaqueJSScript> create(VM* vm, const String& url, int startingLineNumber, const String& source) + { + return WTF::adoptRef(new OpaqueJSScript(vm, url, startingLineNumber, source)); + } + + const String& source() const OVERRIDE + { + return m_source; + } + + VM* vm() const { return m_vm; } + +private: + OpaqueJSScript(VM* vm, const String& url, int startingLineNumber, const String& source) + : SourceProvider(url, TextPosition(OrdinalNumber::fromOneBasedInt(startingLineNumber), OrdinalNumber::first())) + , m_vm(vm) + , m_source(source) + { + } + + ~OpaqueJSScript() { } + + VM* m_vm; + String m_source; +}; + +static bool parseScript(VM* vm, const SourceCode& source, ParserError& error) +{ + return JSC::parse<JSC::ProgramNode>(vm, source, 0, Identifier(), JSParseNormal, JSParseProgramCode, error); +} + +extern "C" { + +JSScriptRef JSScriptCreateReferencingImmortalASCIIText(JSContextGroupRef contextGroup, JSStringRef url, int startingLineNumber, const char* source, size_t length, JSStringRef* errorMessage, int* errorLine) +{ + VM* vm = toJS(contextGroup); + APIEntryShim entryShim(vm); + for (size_t i = 0; i < length; i++) { + if (!isASCII(source[i])) + return 0; + } + + RefPtr<OpaqueJSScript> result = OpaqueJSScript::create(vm, url->string(), startingLineNumber, String(StringImpl::createFromLiteral(source, length))); + + ParserError error; + if (!parseScript(vm, SourceCode(result), error)) { + if (errorMessage) + *errorMessage = OpaqueJSString::create(error.m_message).leakRef(); + if (errorLine) + *errorLine = error.m_line; + return 0; + } + + return result.release().leakRef(); +} + +JSScriptRef JSScriptCreateFromString(JSContextGroupRef contextGroup, JSStringRef url, int startingLineNumber, JSStringRef source, JSStringRef* errorMessage, int* errorLine) +{ + VM* vm = toJS(contextGroup); + APIEntryShim entryShim(vm); + + RefPtr<OpaqueJSScript> result = OpaqueJSScript::create(vm, url->string(), startingLineNumber, source->string()); + + ParserError error; + if (!parseScript(vm, SourceCode(result), error)) { + if (errorMessage) + *errorMessage = OpaqueJSString::create(error.m_message).leakRef(); + if (errorLine) + *errorLine = error.m_line; + return 0; + } + + return result.release().leakRef(); +} + +void JSScriptRetain(JSScriptRef script) +{ + APIEntryShim entryShim(script->vm()); + script->ref(); +} + +void JSScriptRelease(JSScriptRef script) +{ + APIEntryShim entryShim(script->vm()); + script->deref(); +} + +JSValueRef JSScriptEvaluate(JSContextRef context, JSScriptRef script, JSValueRef thisValueRef, JSValueRef* exception) +{ + ExecState* exec = toJS(context); + APIEntryShim entryShim(exec); + if (script->vm() != &exec->vm()) { + RELEASE_ASSERT_NOT_REACHED(); + return 0; + } + JSValue internalException; + JSValue thisValue = thisValueRef ? toJS(exec, thisValueRef) : jsUndefined(); + JSValue result = evaluate(exec, SourceCode(script), thisValue, &internalException); + if (internalException) { + if (exception) + *exception = toRef(exec, internalException); + return 0; + } + ASSERT(result); + return toRef(exec, result); +} + +} diff --git a/Source/JavaScriptCore/API/JSScriptRefPrivate.h b/Source/JavaScriptCore/API/JSScriptRefPrivate.h new file mode 100644 index 000000000..e1992052a --- /dev/null +++ b/Source/JavaScriptCore/API/JSScriptRefPrivate.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2012 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSScriptRefPrivate_h +#define JSScriptRefPrivate_h + +#include <JavaScriptCore/JSContextRef.h> +#include <JavaScriptCore/JSStringRef.h> +#include <JavaScriptCore/JSValueRef.h> + +/*! @typedef JSScriptRef A JavaScript script reference. */ +typedef struct OpaqueJSScript* JSScriptRef; + +#ifdef __cplusplus +extern "C" { +#endif + +/*! + @function + @abstract Creates a script reference from an ascii string, without copying or taking ownership of the string + @param contextGroup The context group the script is to be used in. + @param url The source url to be reported in errors and exceptions. + @param startingLineNumber An integer value specifying the script's starting line number in the file located at sourceURL. This is only used when reporting exceptions. + @param source The source string. This is required to be pure ASCII and to never be deallocated. + @param length The length of the source string. + @param errorMessage A pointer to a JSStringRef in which to store the parse error message if the source is not valid. Pass NULL if you do not care to store an error message. + @param errorLine A pointer to an int in which to store the line number of a parser error. Pass NULL if you do not care to store an error line. + @result A JSScriptRef for the provided source, or NULL if any non-ASCII character is found in source or if the source is not a valid JavaScript program. Ownership follows the Create Rule. + @discussion Use this function to create a reusable script reference with a constant + buffer as the backing string. The source string must outlive the global context. + */ +JS_EXPORT JSScriptRef JSScriptCreateReferencingImmortalASCIIText(JSContextGroupRef contextGroup, JSStringRef url, int startingLineNumber, const char* source, size_t length, JSStringRef* errorMessage, int* errorLine); + +/*! + @function + @abstract Creates a script reference from a string + @param contextGroup The context group the script is to be used in. + @param url The source url to be reported in errors and exceptions. + @param startingLineNumber An integer value specifying the script's starting line number in the file located at sourceURL. This is only used when reporting exceptions. + @param source The source string. + @param errorMessage A pointer to a JSStringRef in which to store the parse error message if the source is not valid. Pass NULL if you do not care to store an error message. + @param errorLine A pointer to an int in which to store the line number of a parser error. Pass NULL if you do not care to store an error line. + @result A JSScriptRef for the provided source, or NULL is the source is not a valid JavaScript program. Ownership follows the Create Rule. + */ +JS_EXPORT JSScriptRef JSScriptCreateFromString(JSContextGroupRef contextGroup, JSStringRef url, int startingLineNumber, JSStringRef source, JSStringRef* errorMessage, int* errorLine); + +/*! + @function + @abstract Retains a JavaScript script. + @param script The script to retain. + */ +JS_EXPORT void JSScriptRetain(JSScriptRef script); + +/*! + @function + @abstract Releases a JavaScript script. + @param script The script to release. + */ +JS_EXPORT void JSScriptRelease(JSScriptRef script); + +/*! + @function + @abstract Evaluates a JavaScript script. + @param ctx The execution context to use. + @param script The JSScript to evaluate. + @param thisValue The value to use as "this" when evaluating the script. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result The JSValue that results from evaluating script, or NULL if an exception is thrown. + */ +JS_EXPORT JSValueRef JSScriptEvaluate(JSContextRef ctx, JSScriptRef script, JSValueRef thisValue, JSValueRef* exception); + + +#ifdef __cplusplus +} +#endif + +#endif /* JSScriptRefPrivate_h */ diff --git a/Source/JavaScriptCore/API/JSStringRef.cpp b/Source/JavaScriptCore/API/JSStringRef.cpp index 7f2168dc2..a03afed5b 100644 --- a/Source/JavaScriptCore/API/JSStringRef.cpp +++ b/Source/JavaScriptCore/API/JSStringRef.cpp @@ -25,6 +25,7 @@ #include "config.h" #include "JSStringRef.h" +#include "JSStringRefPrivate.h" #include "InitializeThreading.h" #include "OpaqueJSString.h" @@ -55,10 +56,15 @@ JSStringRef JSStringCreateWithUTF8CString(const char* string) } } - // Null string. return OpaqueJSString::create().leakRef(); } +JSStringRef JSStringCreateWithCharactersNoCopy(const JSChar* chars, size_t numChars) +{ + initializeThreading(); + return OpaqueJSString::create(StringImpl::createWithoutCopying(chars, numChars)).leakRef(); +} + JSStringRef JSStringRetain(JSStringRef string) { string->ref(); diff --git a/Source/JavaScriptCore/API/JSStringRefCF.cpp b/Source/JavaScriptCore/API/JSStringRefCF.cpp index fd72a593c..64d2d6251 100644 --- a/Source/JavaScriptCore/API/JSStringRefCF.cpp +++ b/Source/JavaScriptCore/API/JSStringRefCF.cpp @@ -30,7 +30,7 @@ #include "InitializeThreading.h" #include "JSStringRef.h" #include "OpaqueJSString.h" -#include <runtime/JSValue.h> +#include <runtime/JSCJSValue.h> #include <wtf/OwnArrayPtr.h> JSStringRef JSStringCreateWithCFString(CFStringRef string) @@ -51,9 +51,9 @@ JSStringRef JSStringCreateWithCFString(CFStringRef string) CFStringGetCharacters(string, CFRangeMake(0, length), buffer.get()); COMPILE_ASSERT(sizeof(UniChar) == sizeof(UChar), unichar_and_uchar_must_be_same_size); return OpaqueJSString::create(reinterpret_cast<UChar*>(buffer.get()), length).leakRef(); - } else { - return OpaqueJSString::create(static_cast<const LChar*>(0), 0).leakRef(); } + + return OpaqueJSString::create(reinterpret_cast<const LChar*>(""), 0).leakRef(); } CFStringRef JSStringCopyCFString(CFAllocatorRef alloc, JSStringRef string) diff --git a/Source/JavaScriptCore/API/JSStringRefPrivate.h b/Source/JavaScriptCore/API/JSStringRefPrivate.h new file mode 100644 index 000000000..f1db806ee --- /dev/null +++ b/Source/JavaScriptCore/API/JSStringRefPrivate.h @@ -0,0 +1,41 @@ +/* + * 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 JSStringRefPrivate_h +#define JSStringRefPrivate_h + +#include <JavaScriptCore/JSStringRef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +JS_EXPORT JSStringRef JSStringCreateWithCharactersNoCopy(const JSChar* chars, size_t numChars); + +#ifdef __cplusplus +} +#endif + +#endif /* JSStringRefPrivate_h */ diff --git a/Source/JavaScriptCore/API/JSStringRefQt.cpp b/Source/JavaScriptCore/API/JSStringRefQt.cpp new file mode 100644 index 000000000..259bad8e6 --- /dev/null +++ b/Source/JavaScriptCore/API/JSStringRefQt.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2006, 2007 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). + * + * 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 COMPUTER, 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 COMPUTER, 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. + */ + +#include "config.h" +#include "JSStringRefQt.h" + +#include "APICast.h" +#include "InitializeThreading.h" +#include "JSStringRef.h" +#include "OpaqueJSString.h" +#include <runtime/JSCJSValue.h> +#include <wtf/OwnArrayPtr.h> + +QString JSStringCopyQString(JSStringRef string) +{ + return string->qString(); +} + +JSRetainPtr<JSStringRef> JSStringCreateWithQString(const QString& qString) +{ + RefPtr<OpaqueJSString> jsString = OpaqueJSString::create(qString); + + if (jsString) + return JSRetainPtr<JSStringRef>(Adopt, jsString.release().leakRef()); + + return JSRetainPtr<JSStringRef>(Adopt, OpaqueJSString::create().leakRef()); +} diff --git a/Source/JavaScriptCore/API/JSStringRefQt.h b/Source/JavaScriptCore/API/JSStringRefQt.h new file mode 100644 index 000000000..63a059fdb --- /dev/null +++ b/Source/JavaScriptCore/API/JSStringRefQt.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2006, 2007 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). + * + * 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 COMPUTER, 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 COMPUTER, 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 JSStringRefQt_h +#define JSStringRefQt_h + +#include "JSBase.h" +#include "JSRetainPtr.h" +#include <QString> + +/* QString convenience methods */ + +/*! +@function +@abstract Creates a QString from a JavaScript string. +@param string The JSString to copy into the new QString. +@result A QString containing string. +*/ +JS_EXPORT QString JSStringCopyQString(JSStringRef string); +JS_EXPORT JSRetainPtr<JSStringRef> JSStringCreateWithQString(const QString&); + +#endif /* JSStringRefQt_h */ diff --git a/Source/JavaScriptCore/API/JSValue.h b/Source/JavaScriptCore/API/JSValue.h new file mode 100644 index 000000000..c77707538 --- /dev/null +++ b/Source/JavaScriptCore/API/JSValue.h @@ -0,0 +1,306 @@ +/* + * 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 JSValue_h +#define JSValue_h + +#if JSC_OBJC_API_ENABLED + +@class JSContext; + +// A JSValue is a reference to a value within the JavaScript object space of a +// JSVirtualMachine. All instances of JSValue originate from a JSContext and +// hold a strong reference to this JSContext. As long as any value associated with +// a particular JSContext is retained, that JSContext will remain alive. +// Where an instance method is invoked upon a JSValue, and this returns another +// JSValue, the returned JSValue will originate from the same JSContext as the +// JSValue on which the method was invoked. +// +// For all methods taking arguments of type id, arguments will be converted +// into a JavaScript value according to the conversion specified below. +// All JavaScript values are associated with a particular JSVirtualMachine +// (the associated JSVirtualMachine is available indirectly via the context +// property). An instance of JSValue may only be passed as an argument to +// methods on instances of JSValue and JSContext that belong to the same +// JSVirtualMachine - passing a JSValue to a method on an object originating +// from a different JSVirtualMachine will result in an Objective-C exception +// being raised. +// +// Conversion between Objective-C and JavaScript types. +// +// When converting between JavaScript values and Objective-C objects a copy is +// performed. Values of types listed below are copied to the corresponding +// types on conversion in each direction. For NSDictionaries, entries in the +// dictionary that are keyed by strings are copied onto a JavaScript object. +// For dictionaries and arrays, conversion is recursive, with the same object +// conversion being applied to all entries in the collection. +// +// Objective-C type | JavaScript type +// --------------------+--------------------- +// nil | undefined +// NSNull | null +// NSString | string +// NSNumber | number, boolean +// NSDictionary | Object object +// NSArray | Array object +// NSDate | Date object +// NSBlock * | Function object * +// id ** | Wrapper object ** +// Class *** | Constructor object *** +// +// * Instances of NSBlock with supported arguments types will be presented to +// JavaScript as a callable Function object. For more information on supported +// argument types see JSExport.h. If a JavaScript Function originating from an +// Objective-C block is converted back to an Objective-C object the block will +// be returned. All other JavaScript functions will be converted in the same +// manner as a JavaScript object of type Object. +// +// ** For Objective-C instances that do not derive from the set of types listed +// above, a wrapper object to provide a retaining handle to the Objective-C +// instance from JavaScript. For more information on these wrapper objects, see +// JSExport.h. When a JavaScript wrapper object is converted back to Objective-C +// the Objective-C instance being retained by the wrapper is returned. +// +// *** For Objective-C Class objects a constructor object containing exported +// class methods will be returned. See JSExport.h for more information on +// constructor objects. + +NS_CLASS_AVAILABLE(10_9, NA) +@interface JSValue : NSObject + +// Create a JSValue by converting an Objective-C object. ++ (JSValue *)valueWithObject:(id)value inContext:(JSContext *)context; +// Create a JavaScript value from an Objective-C primitive type. ++ (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context; ++ (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context; ++ (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context; ++ (JSValue *)valueWithUInt32:(uint32_t)value inContext:(JSContext *)context; +// Create a JavaScript value in this context. ++ (JSValue *)valueWithNewObjectInContext:(JSContext *)context; ++ (JSValue *)valueWithNewArrayInContext:(JSContext *)context; ++ (JSValue *)valueWithNewRegularExpressionFromPattern:(NSString *)pattern flags:(NSString *)flags inContext:(JSContext *)context; ++ (JSValue *)valueWithNewErrorFromMessage:(NSString *)message inContext:(JSContext *)context; ++ (JSValue *)valueWithNullInContext:(JSContext *)context; ++ (JSValue *)valueWithUndefinedInContext:(JSContext *)context; + +// Convert this value to a corresponding Objective-C object, according to the +// conversion specified above. +- (id)toObject; +// Convert this value to a corresponding Objective-C object, if the result is +// not of the specified class then nil will be returned. +- (id)toObjectOfClass:(Class)expectedClass; +// The value is copied to a boolean according to the conversion specified by the +// JavaScript language. +- (BOOL)toBool; +// The value is copied to a number according to the conversion specified by the +// JavaScript language. +- (double)toDouble; +// The value is copied to an integer according to the conversion specified by +// the JavaScript language. +- (int32_t)toInt32; +// The value is copied to an integer according to the conversion specified by +// the JavaScript language. +- (uint32_t)toUInt32; +// If the value is a boolean, a NSNumber value of @YES or @NO will be returned. +// For all other types the value will be copied to a number according to the +// conversion specified by the JavaScript language. +- (NSNumber *)toNumber; +// The value is copied to a string according to the conversion specified by the +// JavaScript language. +- (NSString *)toString; +// The value is converted to a number representing a time interval since 1970, +// and a new NSDate instance is returned. +- (NSDate *)toDate; +// If the value is null or undefined then nil is returned. +// If the value is not an object then a JavaScript TypeError will be thrown. +// The property "length" is read from the object, converted to an unsigned +// integer, and an NSArray of this size is allocated. Properties corresponding +// to indicies within the array bounds will be copied to the array, with +// Objective-C objects converted to equivalent JSValues as specified. +- (NSArray *)toArray; +// If the value is null or undefined then nil is returned. +// If the value is not an object then a JavaScript TypeError will be thrown. +// All enumerable properties of the object are copied to the dictionary, with +// Objective-C objects converted to equivalent JSValues as specified. +- (NSDictionary *)toDictionary; + +// Access a property from the value. This method will return the JavaScript value +// 'undefined' if the property does not exist. +- (JSValue *)valueForProperty:(NSString *)property; +// Set a property on the value. +- (void)setValue:(id)value forProperty:(NSString *)property; +// Delete a property from the value, returns YES if deletion is successful. +- (BOOL)deleteProperty:(NSString *)property; +// Returns YES if property is present on the value. +// This method has the same function as the JavaScript operator "in". +- (BOOL)hasProperty:(NSString *)property; +// This method may be used to create a data or accessor property on an object; +// this method operates in accordance with the Object.defineProperty method in +// the JavaScript language. +- (void)defineProperty:(NSString *)property descriptor:(id)descriptor; + +// Access an indexed property from the value. This method will return the +// JavaScript value 'undefined' if no property exists at that index. +- (JSValue *)valueAtIndex:(NSUInteger)index; +// Set an indexed property on the value. For JSValues that are JavaScript arrays, +// indices greater than UINT_MAX - 1 will not affect the length of the array. +- (void)setValue:(id)value atIndex:(NSUInteger)index; + +// All JavaScript values are precisely one of these types. +- (BOOL)isUndefined; +- (BOOL)isNull; +- (BOOL)isBoolean; +- (BOOL)isNumber; +- (BOOL)isString; +- (BOOL)isObject; + +// This method has the same function as the JavaScript operator "===". +- (BOOL)isEqualToObject:(id)value; +// This method has the same function as the JavaScript operator "==". +- (BOOL)isEqualWithTypeCoercionToObject:(id)value; +// This method has the same function as the JavaScript operator "instanceof". +- (BOOL)isInstanceOf:(id)value; + +// Call this value as a function passing the specified arguments. +- (JSValue *)callWithArguments:(NSArray *)arguments; +// Call this value as a constructor passing the specified arguments. +- (JSValue *)constructWithArguments:(NSArray *)arguments; +// Access the property named "method" from this value; call the value resulting +// from the property access as a function, passing this value as the "this" +// value, and the specified arguments. +- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments; + +// The JSContext that this value originates from. +@property(readonly, retain) JSContext *context; + +@end + +// Objective-C methods exported to JavaScript may have argument and/or return +// values of struct types, provided that conversion to and from the struct is +// supported by JSValue. Support is provided for any types where JSValue +// contains both a class method "valueWith<Type>:inContext:", and and instance +// method "to<Type>" - where the string "<Type>" in these selector names match, +// with the first argument to the former being of the same struct type as the +// return type of the latter. +// Support is provided for structs of type CGPoint, NSRange, CGRect and CGSize. +@interface JSValue(StructSupport) + +// This method returns a newly allocated JavaScript object containing properties +// named "x" and "y", with values from the CGPoint. ++ (JSValue *)valueWithPoint:(CGPoint)point inContext:(JSContext *)context; +// This method returns a newly allocated JavaScript object containing properties +// named "location" and "length", with values from the NSRange. ++ (JSValue *)valueWithRange:(NSRange)range inContext:(JSContext *)context; +// This method returns a newly allocated JavaScript object containing properties +// named "x", "y", "width", and "height", with values from the CGRect. ++ (JSValue *)valueWithRect:(CGRect)rect inContext:(JSContext *)context; +// This method returns a newly allocated JavaScript object containing properties +// named "width" and "height", with values from the CGSize. ++ (JSValue *)valueWithSize:(CGSize)size inContext:(JSContext *)context; + +// Convert a value to type CGPoint by reading properties named "x" and "y" from +// this value, and converting the results to double. +- (CGPoint)toPoint; +// Convert a value to type NSRange by accessing properties named "location" and +// "length" from this value converting the results to double. +- (NSRange)toRange; +// Convert a value to type CGRect by reading properties named "x", "y", "width", +// and "height" from this value, and converting the results to double. +- (CGRect)toRect; +// Convert a value to type CGSize by accessing properties named "width" and +// "height" from this value converting the results to double. +- (CGSize)toSize; + +@end + +// Instances of JSValue implement the following methods in order to enable +// support for subscript access by key and index, for example: +// +// JSValue *objectA, *objectB; +// JSValue *v1 = object[@"X"]; // Get value for property "X" from 'object'. +// JSValue *v2 = object[42]; // Get value for index 42 from 'object'. +// object[@"Y"] = v1; // Assign 'v1' to property "Y" of 'object'. +// object[101] = v2; // Assign 'v2' to index 101 of 'object'. +// +// An object key passed as a subscript will be converted to a JavaScript value, +// and then the value converted to a string used as a property name. +@interface JSValue(SubscriptSupport) + +- (JSValue *)objectForKeyedSubscript:(id)key; +- (JSValue *)objectAtIndexedSubscript:(NSUInteger)index; +- (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying> *)key; +- (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index; + +@end + +// These functions are for bridging between the C API and the Objective-C API. +@interface JSValue(JSValueRefSupport) +// Creates a JSValue, wrapping its C API counterpart. ++ (JSValue *)valueWithJSValueRef:(JSValueRef)value inContext:(JSContext *)context; +// Returns the C API counterpart wrapped by a JSContext. +- (JSValueRef)JSValueRef; +@end + +#ifdef __cplusplus +extern "C" { +#endif + +// These keys may assist in creating a property descriptor for use with the +// defineProperty method on JSValue. +// Property descriptors must fit one of three descriptions: +// Data Descriptor: +// - A descriptor containing one or both of the keys "value" and "writable", +// and optionally containing one or both of the keys "enumerable" and +// "configurable". A data descriptor may not contain either the "get" or +// "set" key. +// A data descriptor may be used to create or modify the attributes of a +// data property on an object (replacing any existing accessor property). +// Accessor Descriptor: +// - A descriptor containing one or both of the keys "get" and "set", and +// optionally containing one or both of the keys "enumerable" and +// "configurable". An accessor descriptor may not contain either the "value" +// or "writable" key. +// An accessor descriptor may be used to create or modify the attributes of +// an accessor property on an object (replacing any existing data property). +// Generic Descriptor: +// - A descriptor containing one or both of the keys "enumerable" and +// "configurable". A generic descriptor may not contain any of the keys +// "value", " writable", "get", or "set". +// A generic descriptor may be used to modify the attributes of an existing +// data or accessor property, or to create a new data property. +JS_EXPORT extern NSString * const JSPropertyDescriptorWritableKey; +JS_EXPORT extern NSString * const JSPropertyDescriptorEnumerableKey; +JS_EXPORT extern NSString * const JSPropertyDescriptorConfigurableKey; +JS_EXPORT extern NSString * const JSPropertyDescriptorValueKey; +JS_EXPORT extern NSString * const JSPropertyDescriptorGetKey; +JS_EXPORT extern NSString * const JSPropertyDescriptorSetKey; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif + +#endif // JSValue_h diff --git a/Source/JavaScriptCore/API/JSValue.mm b/Source/JavaScriptCore/API/JSValue.mm new file mode 100644 index 000000000..e708cc674 --- /dev/null +++ b/Source/JavaScriptCore/API/JSValue.mm @@ -0,0 +1,1131 @@ +/* + * 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. + */ + +#include "config.h" + +#import "APICast.h" +#import "APIShims.h" +#import "DateInstance.h" +#import "Error.h" +#import "JavaScriptCore.h" +#import "JSContextInternal.h" +#import "JSVirtualMachineInternal.h" +#import "JSValueInternal.h" +#import "JSWrapperMap.h" +#import "ObjcRuntimeExtras.h" +#import "Operations.h" +#import "JSCJSValue.h" +#import <wtf/HashMap.h> +#import <wtf/HashSet.h> +#import <wtf/Vector.h> +#import <wtf/TCSpinLock.h> +#import <wtf/text/WTFString.h> +#import <wtf/text/StringHash.h> + +#if JSC_OBJC_API_ENABLED + +NSString * const JSPropertyDescriptorWritableKey = @"writable"; +NSString * const JSPropertyDescriptorEnumerableKey = @"enumerable"; +NSString * const JSPropertyDescriptorConfigurableKey = @"configurable"; +NSString * const JSPropertyDescriptorValueKey = @"value"; +NSString * const JSPropertyDescriptorGetKey = @"get"; +NSString * const JSPropertyDescriptorSetKey = @"set"; + +@implementation JSValue { + JSValueRef m_value; +} + +- (JSValueRef)JSValueRef +{ + return m_value; +} + ++ (JSValue *)valueWithObject:(id)value inContext:(JSContext *)context +{ + return [JSValue valueWithJSValueRef:objectToValue(context, value) inContext:context]; +} + ++ (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context +{ + return [JSValue valueWithJSValueRef:JSValueMakeBoolean([context JSGlobalContextRef], value) inContext:context]; +} + ++ (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context +{ + return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context]; +} + ++ (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context +{ + return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context]; +} + ++ (JSValue *)valueWithUInt32:(uint32_t)value inContext:(JSContext *)context +{ + return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context]; +} + ++ (JSValue *)valueWithNewObjectInContext:(JSContext *)context +{ + return [JSValue valueWithJSValueRef:JSObjectMake([context JSGlobalContextRef], 0, 0) inContext:context]; +} + ++ (JSValue *)valueWithNewArrayInContext:(JSContext *)context +{ + return [JSValue valueWithJSValueRef:JSObjectMakeArray([context JSGlobalContextRef], 0, NULL, 0) inContext:context]; +} + ++ (JSValue *)valueWithNewRegularExpressionFromPattern:(NSString *)pattern flags:(NSString *)flags inContext:(JSContext *)context +{ + JSStringRef patternString = JSStringCreateWithCFString((CFStringRef)pattern); + JSStringRef flagsString = JSStringCreateWithCFString((CFStringRef)flags); + JSValueRef arguments[2] = { JSValueMakeString([context JSGlobalContextRef], patternString), JSValueMakeString([context JSGlobalContextRef], flagsString) }; + JSStringRelease(patternString); + JSStringRelease(flagsString); + + return [JSValue valueWithJSValueRef:JSObjectMakeRegExp([context JSGlobalContextRef], 2, arguments, 0) inContext:context]; +} + ++ (JSValue *)valueWithNewErrorFromMessage:(NSString *)message inContext:(JSContext *)context +{ + JSStringRef string = JSStringCreateWithCFString((CFStringRef)message); + JSValueRef argument = JSValueMakeString([context JSGlobalContextRef], string); + JSStringRelease(string); + + return [JSValue valueWithJSValueRef:JSObjectMakeError([context JSGlobalContextRef], 1, &argument, 0) inContext:context]; +} + ++ (JSValue *)valueWithNullInContext:(JSContext *)context +{ + return [JSValue valueWithJSValueRef:JSValueMakeNull([context JSGlobalContextRef]) inContext:context]; +} + ++ (JSValue *)valueWithUndefinedInContext:(JSContext *)context +{ + return [JSValue valueWithJSValueRef:JSValueMakeUndefined([context JSGlobalContextRef]) inContext:context]; +} + +- (id)toObject +{ + return valueToObject(_context, m_value); +} + +- (id)toObjectOfClass:(Class)expectedClass +{ + id result = [self toObject]; + return [result isKindOfClass:expectedClass] ? result : nil; +} + +- (BOOL)toBool +{ + return JSValueToBoolean([_context JSGlobalContextRef], m_value); +} + +- (double)toDouble +{ + JSValueRef exception = 0; + double result = JSValueToNumber([_context JSGlobalContextRef], m_value, &exception); + if (exception) { + [_context notifyException:exception]; + return std::numeric_limits<double>::quiet_NaN(); + } + + return result; +} + +- (int32_t)toInt32 +{ + return JSC::toInt32([self toDouble]); +} + +- (uint32_t)toUInt32 +{ + return JSC::toUInt32([self toDouble]); +} + +- (NSNumber *)toNumber +{ + JSValueRef exception = 0; + id result = valueToNumber([_context JSGlobalContextRef], m_value, &exception); + if (exception) + [_context notifyException:exception]; + return result; +} + +- (NSString *)toString +{ + JSValueRef exception = 0; + id result = valueToString([_context JSGlobalContextRef], m_value, &exception); + if (exception) + [_context notifyException:exception]; + return result; +} + +- (NSDate *)toDate +{ + JSValueRef exception = 0; + id result = valueToDate([_context JSGlobalContextRef], m_value, &exception); + if (exception) + [_context notifyException:exception]; + return result; +} + +- (NSArray *)toArray +{ + JSValueRef exception = 0; + id result = valueToArray([_context JSGlobalContextRef], m_value, &exception); + if (exception) + [_context notifyException:exception]; + return result; +} + +- (NSDictionary *)toDictionary +{ + JSValueRef exception = 0; + id result = valueToDictionary([_context JSGlobalContextRef], m_value, &exception); + if (exception) + [_context notifyException:exception]; + return result; +} + +- (JSValue *)valueForProperty:(NSString *)propertyName +{ + JSValueRef exception = 0; + JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName); + JSValueRef result = JSObjectGetProperty([_context JSGlobalContextRef], object, name, &exception); + JSStringRelease(name); + if (exception) + return [_context valueFromNotifyException:exception]; + + return [JSValue valueWithJSValueRef:result inContext:_context]; +} + +- (void)setValue:(id)value forProperty:(NSString *)propertyName +{ + JSValueRef exception = 0; + JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); + if (exception) { + [_context notifyException:exception]; + return; + } + + JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName); + JSObjectSetProperty([_context JSGlobalContextRef], object, name, objectToValue(_context, value), 0, &exception); + JSStringRelease(name); + if (exception) { + [_context notifyException:exception]; + return; + } +} + +- (BOOL)deleteProperty:(NSString *)propertyName +{ + JSValueRef exception = 0; + JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); + if (exception) + return [_context boolFromNotifyException:exception]; + + JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName); + BOOL result = JSObjectDeleteProperty([_context JSGlobalContextRef], object, name, &exception); + JSStringRelease(name); + if (exception) + return [_context boolFromNotifyException:exception]; + + return result; +} + +- (BOOL)hasProperty:(NSString *)propertyName +{ + JSValueRef exception = 0; + JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); + if (exception) + return [_context boolFromNotifyException:exception]; + + JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName); + BOOL result = JSObjectHasProperty([_context JSGlobalContextRef], object, name); + JSStringRelease(name); + return result; +} + +- (void)defineProperty:(NSString *)property descriptor:(id)descriptor +{ + [[_context globalObject][@"Object"] invokeMethod:@"defineProperty" withArguments:@[ self, property, descriptor ]]; +} + +- (JSValue *)valueAtIndex:(NSUInteger)index +{ + // Properties that are higher than an unsigned value can hold are converted to a double then inserted as a normal property. + // Indices that are bigger than the max allowed index size (UINT_MAX - 1) will be handled internally in get(). + if (index != (unsigned)index) + return [self valueForProperty:[[JSValue valueWithDouble:index inContext:_context] toString]]; + + JSValueRef exception = 0; + JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + JSValueRef result = JSObjectGetPropertyAtIndex([_context JSGlobalContextRef], object, (unsigned)index, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + return [JSValue valueWithJSValueRef:result inContext:_context]; +} + +- (void)setValue:(id)value atIndex:(NSUInteger)index +{ + // Properties that are higher than an unsigned value can hold are converted to a double, then inserted as a normal property. + // Indices that are bigger than the max allowed index size (UINT_MAX - 1) will be handled internally in putByIndex(). + if (index != (unsigned)index) + return [self setValue:value forProperty:[[JSValue valueWithDouble:index inContext:_context] toString]]; + + JSValueRef exception = 0; + JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); + if (exception) { + [_context notifyException:exception]; + return; + } + + JSObjectSetPropertyAtIndex([_context JSGlobalContextRef], object, (unsigned)index, objectToValue(_context, value), &exception); + if (exception) { + [_context notifyException:exception]; + return; + } +} + +- (BOOL)isUndefined +{ + return JSValueIsUndefined([_context JSGlobalContextRef], m_value); +} + +- (BOOL)isNull +{ + return JSValueIsNull([_context JSGlobalContextRef], m_value); +} + +- (BOOL)isBoolean +{ + return JSValueIsBoolean([_context JSGlobalContextRef], m_value); +} + +- (BOOL)isNumber +{ + return JSValueIsNumber([_context JSGlobalContextRef], m_value); +} + +- (BOOL)isString +{ + return JSValueIsString([_context JSGlobalContextRef], m_value); +} + +- (BOOL)isObject +{ + return JSValueIsObject([_context JSGlobalContextRef], m_value); +} + +- (BOOL)isEqualToObject:(id)value +{ + return JSValueIsStrictEqual([_context JSGlobalContextRef], m_value, objectToValue(_context, value)); +} + +- (BOOL)isEqualWithTypeCoercionToObject:(id)value +{ + JSValueRef exception = 0; + BOOL result = JSValueIsEqual([_context JSGlobalContextRef], m_value, objectToValue(_context, value), &exception); + if (exception) + return [_context boolFromNotifyException:exception]; + + return result; +} + +- (BOOL)isInstanceOf:(id)value +{ + JSValueRef exception = 0; + JSObjectRef constructor = JSValueToObject([_context JSGlobalContextRef], objectToValue(_context, value), &exception); + if (exception) + return [_context boolFromNotifyException:exception]; + + BOOL result = JSValueIsInstanceOfConstructor([_context JSGlobalContextRef], m_value, constructor, &exception); + if (exception) + return [_context boolFromNotifyException:exception]; + + return result; +} + +- (JSValue *)callWithArguments:(NSArray *)argumentArray +{ + NSUInteger argumentCount = [argumentArray count]; + JSValueRef arguments[argumentCount]; + for (unsigned i = 0; i < argumentCount; ++i) + arguments[i] = objectToValue(_context, [argumentArray objectAtIndex:i]); + + JSValueRef exception = 0; + JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + JSValueRef result = JSObjectCallAsFunction([_context JSGlobalContextRef], object, 0, argumentCount, arguments, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + return [JSValue valueWithJSValueRef:result inContext:_context]; +} + +- (JSValue *)constructWithArguments:(NSArray *)argumentArray +{ + NSUInteger argumentCount = [argumentArray count]; + JSValueRef arguments[argumentCount]; + for (unsigned i = 0; i < argumentCount; ++i) + arguments[i] = objectToValue(_context, [argumentArray objectAtIndex:i]); + + JSValueRef exception = 0; + JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + JSObjectRef result = JSObjectCallAsConstructor([_context JSGlobalContextRef], object, argumentCount, arguments, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + return [JSValue valueWithJSValueRef:result inContext:_context]; +} + +- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments +{ + NSUInteger argumentCount = [arguments count]; + JSValueRef argumentArray[argumentCount]; + for (unsigned i = 0; i < argumentCount; ++i) + argumentArray[i] = objectToValue(_context, [arguments objectAtIndex:i]); + + JSValueRef exception = 0; + JSObjectRef thisObject = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + JSStringRef name = JSStringCreateWithCFString((CFStringRef)method); + JSValueRef function = JSObjectGetProperty([_context JSGlobalContextRef], thisObject, name, &exception); + JSStringRelease(name); + if (exception) + return [_context valueFromNotifyException:exception]; + + JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], function, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + JSValueRef result = JSObjectCallAsFunction([_context JSGlobalContextRef], object, thisObject, argumentCount, argumentArray, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + return [JSValue valueWithJSValueRef:result inContext:_context]; +} + +@end + +@implementation JSValue(StructSupport) + +- (CGPoint)toPoint +{ + return (CGPoint){ + [self[@"x"] toDouble], + [self[@"y"] toDouble] + }; +} + +- (NSRange)toRange +{ + return (NSRange){ + [[self[@"location"] toNumber] unsignedIntegerValue], + [[self[@"length"] toNumber] unsignedIntegerValue] + }; +} + +- (CGRect)toRect +{ + return (CGRect){ + [self toPoint], + [self toSize] + }; +} + +- (CGSize)toSize +{ + return (CGSize){ + [self[@"width"] toDouble], + [self[@"height"] toDouble] + }; +} + ++ (JSValue *)valueWithPoint:(CGPoint)point inContext:(JSContext *)context +{ + return [JSValue valueWithObject:@{ + @"x":@(point.x), + @"y":@(point.y) + } inContext:context]; +} + ++ (JSValue *)valueWithRange:(NSRange)range inContext:(JSContext *)context +{ + return [JSValue valueWithObject:@{ + @"location":@(range.location), + @"length":@(range.length) + } inContext:context]; +} + ++ (JSValue *)valueWithRect:(CGRect)rect inContext:(JSContext *)context +{ + return [JSValue valueWithObject:@{ + @"x":@(rect.origin.x), + @"y":@(rect.origin.y), + @"width":@(rect.size.width), + @"height":@(rect.size.height) + } inContext:context]; +} + ++ (JSValue *)valueWithSize:(CGSize)size inContext:(JSContext *)context +{ + return [JSValue valueWithObject:@{ + @"width":@(size.width), + @"height":@(size.height) + } inContext:context]; +} + +@end + +@implementation JSValue(SubscriptSupport) + +- (JSValue *)objectForKeyedSubscript:(id)key +{ + if (![key isKindOfClass:[NSString class]]) { + key = [[JSValue valueWithObject:key inContext:_context] toString]; + if (!key) + return [JSValue valueWithUndefinedInContext:_context]; + } + + return [self valueForProperty:(NSString *)key]; +} + +- (JSValue *)objectAtIndexedSubscript:(NSUInteger)index +{ + return [self valueAtIndex:index]; +} + +- (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying> *)key +{ + if (![key isKindOfClass:[NSString class]]) { + key = [[JSValue valueWithObject:key inContext:_context] toString]; + if (!key) + return; + } + + [self setValue:object forProperty:(NSString *)key]; +} + +- (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index +{ + [self setValue:object atIndex:index]; +} + +@end + +inline bool isDate(JSObjectRef object, JSGlobalContextRef context) +{ + JSC::APIEntryShim entryShim(toJS(context)); + return toJS(object)->inherits(&JSC::DateInstance::s_info); +} + +inline bool isArray(JSObjectRef object, JSGlobalContextRef context) +{ + JSC::APIEntryShim entryShim(toJS(context)); + return toJS(object)->inherits(&JSC::JSArray::s_info); +} + +@implementation JSValue(Internal) + +enum ConversionType { + ContainerNone, + ContainerArray, + ContainerDictionary +}; + +class JSContainerConvertor { +public: + struct Task { + JSValueRef js; + id objc; + ConversionType type; + }; + + JSContainerConvertor(JSGlobalContextRef context) + : m_context(context) + { + } + + id convert(JSValueRef property); + void add(Task); + Task take(); + bool isWorkListEmpty() const { return !m_worklist.size(); } + +private: + JSGlobalContextRef m_context; + HashMap<JSValueRef, id> m_objectMap; + Vector<Task> m_worklist; +}; + +inline id JSContainerConvertor::convert(JSValueRef value) +{ + HashMap<JSValueRef, id>::iterator iter = m_objectMap.find(value); + if (iter != m_objectMap.end()) + return iter->value; + + Task result = valueToObjectWithoutCopy(m_context, value); + if (result.js) + add(result); + return result.objc; +} + +void JSContainerConvertor::add(Task task) +{ + m_objectMap.add(task.js, task.objc); + if (task.type != ContainerNone) + m_worklist.append(task); +} + +JSContainerConvertor::Task JSContainerConvertor::take() +{ + ASSERT(!isWorkListEmpty()); + Task last = m_worklist.last(); + m_worklist.removeLast(); + return last; +} + +static JSContainerConvertor::Task valueToObjectWithoutCopy(JSGlobalContextRef context, JSValueRef value) +{ + if (!JSValueIsObject(context, value)) { + id primitive; + if (JSValueIsBoolean(context, value)) + primitive = JSValueToBoolean(context, value) ? @YES : @NO; + else if (JSValueIsNumber(context, value)) { + // Normalize the number, so it will unique correctly in the hash map - + // it's nicer not to leak this internal implementation detail! + value = JSValueMakeNumber(context, JSValueToNumber(context, value, 0)); + primitive = [NSNumber numberWithDouble:JSValueToNumber(context, value, 0)]; + } else if (JSValueIsString(context, value)) { + // Would be nice to unique strings, too. + JSStringRef jsstring = JSValueToStringCopy(context, value, 0); + NSString * stringNS = (NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsstring); + JSStringRelease(jsstring); + primitive = [stringNS autorelease]; + } else if (JSValueIsNull(context, value)) + primitive = [NSNull null]; + else { + ASSERT(JSValueIsUndefined(context, value)); + primitive = nil; + } + return (JSContainerConvertor::Task){ value, primitive, ContainerNone }; + } + + JSObjectRef object = JSValueToObject(context, value, 0); + + if (id wrapped = tryUnwrapObjcObject(context, object)) + return (JSContainerConvertor::Task){ object, wrapped, ContainerNone }; + + if (isDate(object, context)) + return (JSContainerConvertor::Task){ object, [NSDate dateWithTimeIntervalSince1970:JSValueToNumber(context, object, 0)], ContainerNone }; + + if (isArray(object, context)) + return (JSContainerConvertor::Task){ object, [NSMutableArray array], ContainerArray }; + + return (JSContainerConvertor::Task){ object, [NSMutableDictionary dictionary], ContainerDictionary }; +} + +static id containerValueToObject(JSGlobalContextRef context, JSContainerConvertor::Task task) +{ + ASSERT(task.type != ContainerNone); + JSContainerConvertor convertor(context); + convertor.add(task); + ASSERT(!convertor.isWorkListEmpty()); + + do { + JSContainerConvertor::Task current = convertor.take(); + ASSERT(JSValueIsObject(context, current.js)); + JSObjectRef js = JSValueToObject(context, current.js, 0); + + if (current.type == ContainerArray) { + ASSERT([current.objc isKindOfClass:[NSMutableArray class]]); + NSMutableArray *array = (NSMutableArray *)current.objc; + + JSStringRef lengthString = JSStringCreateWithUTF8CString("length"); + unsigned length = JSC::toUInt32(JSValueToNumber(context, JSObjectGetProperty(context, js, lengthString, 0), 0)); + JSStringRelease(lengthString); + + for (unsigned i = 0; i < length; ++i) { + id objc = convertor.convert(JSObjectGetPropertyAtIndex(context, js, i, 0)); + [array addObject:objc ? objc : [NSNull null]]; + } + } else { + ASSERT([current.objc isKindOfClass:[NSMutableDictionary class]]); + NSMutableDictionary *dictionary = (NSMutableDictionary *)current.objc; + + JSPropertyNameArrayRef propertyNameArray = JSObjectCopyPropertyNames(context, js); + size_t length = JSPropertyNameArrayGetCount(propertyNameArray); + + for (size_t i = 0; i < length; ++i) { + JSStringRef propertyName = JSPropertyNameArrayGetNameAtIndex(propertyNameArray, i); + if (id objc = convertor.convert(JSObjectGetProperty(context, js, propertyName, 0))) + dictionary[[(NSString *)JSStringCopyCFString(kCFAllocatorDefault, propertyName) autorelease]] = objc; + } + + JSPropertyNameArrayRelease(propertyNameArray); + } + + } while (!convertor.isWorkListEmpty()); + + return task.objc; +} + +id valueToObject(JSContext *context, JSValueRef value) +{ + JSContainerConvertor::Task result = valueToObjectWithoutCopy([context JSGlobalContextRef], value); + if (result.type == ContainerNone) + return result.objc; + return containerValueToObject([context JSGlobalContextRef], result); +} + +id valueToNumber(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception) +{ + ASSERT(!*exception); + if (id wrapped = tryUnwrapObjcObject(context, value)) { + if ([wrapped isKindOfClass:[NSNumber class]]) + return wrapped; + } + + if (JSValueIsBoolean(context, value)) + return JSValueToBoolean(context, value) ? @YES : @NO; + + double result = JSValueToNumber(context, value, exception); + return [NSNumber numberWithDouble:*exception ? std::numeric_limits<double>::quiet_NaN() : result]; +} + +id valueToString(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception) +{ + ASSERT(!*exception); + if (id wrapped = tryUnwrapObjcObject(context, value)) { + if ([wrapped isKindOfClass:[NSString class]]) + return wrapped; + } + + JSStringRef jsstring = JSValueToStringCopy(context, value, exception); + if (*exception) { + ASSERT(!jsstring); + return nil; + } + + NSString *stringNS = [(NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsstring) autorelease]; + JSStringRelease(jsstring); + return stringNS; +} + +id valueToDate(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception) +{ + ASSERT(!*exception); + if (id wrapped = tryUnwrapObjcObject(context, value)) { + if ([wrapped isKindOfClass:[NSDate class]]) + return wrapped; + } + + double result = JSValueToNumber(context, value, exception); + return *exception ? nil : [NSDate dateWithTimeIntervalSince1970:result]; +} + +id valueToArray(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception) +{ + ASSERT(!*exception); + if (id wrapped = tryUnwrapObjcObject(context, value)) { + if ([wrapped isKindOfClass:[NSArray class]]) + return wrapped; + } + + if (JSValueIsObject(context, value)) + return containerValueToObject(context, (JSContainerConvertor::Task){ value, [NSMutableArray array], ContainerArray}); + + if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value))) + *exception = toRef(JSC::createTypeError(toJS(context), "Cannot convert primitive to NSArray")); + return nil; +} + +id valueToDictionary(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception) +{ + ASSERT(!*exception); + if (id wrapped = tryUnwrapObjcObject(context, value)) { + if ([wrapped isKindOfClass:[NSDictionary class]]) + return wrapped; + } + + if (JSValueIsObject(context, value)) + return containerValueToObject(context, (JSContainerConvertor::Task){ value, [NSMutableDictionary dictionary], ContainerDictionary}); + + if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value))) + *exception = toRef(JSC::createTypeError(toJS(context), "Cannot convert primitive to NSDictionary")); + return nil; +} + +class ObjcContainerConvertor { +public: + struct Task { + id objc; + JSValueRef js; + ConversionType type; + }; + + ObjcContainerConvertor(JSContext *context) + : m_context(context) + { + } + + JSValueRef convert(id object); + void add(Task); + Task take(); + bool isWorkListEmpty() const { return !m_worklist.size(); } + +private: + JSContext *m_context; + HashMap<id, JSValueRef> m_objectMap; + Vector<Task> m_worklist; +}; + +JSValueRef ObjcContainerConvertor::convert(id object) +{ + ASSERT(object); + + auto it = m_objectMap.find(object); + if (it != m_objectMap.end()) + return it->value; + + ObjcContainerConvertor::Task task = objectToValueWithoutCopy(m_context, object); + add(task); + return task.js; +} + +void ObjcContainerConvertor::add(ObjcContainerConvertor::Task task) +{ + m_objectMap.add(task.objc, task.js); + if (task.type != ContainerNone) + m_worklist.append(task); +} + +ObjcContainerConvertor::Task ObjcContainerConvertor::take() +{ + ASSERT(!isWorkListEmpty()); + Task last = m_worklist.last(); + m_worklist.removeLast(); + return last; +} + +inline bool isNSBoolean(id object) +{ + ASSERT([@YES class] == [@NO class]); + ASSERT([@YES class] != [NSNumber class]); + ASSERT([[@YES class] isSubclassOfClass:[NSNumber class]]); + return [object isKindOfClass:[@YES class]]; +} + +static ObjcContainerConvertor::Task objectToValueWithoutCopy(JSContext *context, id object) +{ + JSGlobalContextRef contextRef = [context JSGlobalContextRef]; + + if (!object) + return (ObjcContainerConvertor::Task){ object, JSValueMakeUndefined(contextRef), ContainerNone }; + + if (!class_conformsToProtocol(object_getClass(object), getJSExportProtocol())) { + if ([object isKindOfClass:[NSArray class]]) + return (ObjcContainerConvertor::Task){ object, JSObjectMakeArray(contextRef, 0, NULL, 0), ContainerArray }; + + if ([object isKindOfClass:[NSDictionary class]]) + return (ObjcContainerConvertor::Task){ object, JSObjectMake(contextRef, 0, 0), ContainerDictionary }; + + if ([object isKindOfClass:[NSNull class]]) + return (ObjcContainerConvertor::Task){ object, JSValueMakeNull(contextRef), ContainerNone }; + + if ([object isKindOfClass:[JSValue class]]) + return (ObjcContainerConvertor::Task){ object, ((JSValue *)object)->m_value, ContainerNone }; + + if ([object isKindOfClass:[NSString class]]) { + JSStringRef string = JSStringCreateWithCFString((CFStringRef)object); + JSValueRef js = JSValueMakeString(contextRef, string); + JSStringRelease(string); + return (ObjcContainerConvertor::Task){ object, js, ContainerNone }; + } + + if ([object isKindOfClass:[NSNumber class]]) { + if (isNSBoolean(object)) + return (ObjcContainerConvertor::Task){ object, JSValueMakeBoolean(contextRef, [object boolValue]), ContainerNone }; + return (ObjcContainerConvertor::Task){ object, JSValueMakeNumber(contextRef, [object doubleValue]), ContainerNone }; + } + + if ([object isKindOfClass:[NSDate class]]) { + JSValueRef argument = JSValueMakeNumber(contextRef, [object timeIntervalSince1970]); + JSObjectRef result = JSObjectMakeDate(contextRef, 1, &argument, 0); + return (ObjcContainerConvertor::Task){ object, result, ContainerNone }; + } + + if ([object isKindOfClass:[JSManagedValue class]]) { + JSValue *value = [static_cast<JSManagedValue *>(object) value]; + if (!value) + return (ObjcContainerConvertor::Task) { object, JSValueMakeUndefined(contextRef), ContainerNone }; + return (ObjcContainerConvertor::Task){ object, value->m_value, ContainerNone }; + } + } + + return (ObjcContainerConvertor::Task){ object, valueInternalValue([context wrapperForObjCObject:object]), ContainerNone }; +} + +JSValueRef objectToValue(JSContext *context, id object) +{ + JSGlobalContextRef contextRef = [context JSGlobalContextRef]; + + ObjcContainerConvertor::Task task = objectToValueWithoutCopy(context, object); + if (task.type == ContainerNone) + return task.js; + + ObjcContainerConvertor convertor(context); + convertor.add(task); + ASSERT(!convertor.isWorkListEmpty()); + + do { + ObjcContainerConvertor::Task current = convertor.take(); + ASSERT(JSValueIsObject(contextRef, current.js)); + JSObjectRef js = JSValueToObject(contextRef, current.js, 0); + + if (current.type == ContainerArray) { + ASSERT([current.objc isKindOfClass:[NSArray class]]); + NSArray *array = (NSArray *)current.objc; + NSUInteger count = [array count]; + for (NSUInteger index = 0; index < count; ++index) + JSObjectSetPropertyAtIndex(contextRef, js, index, convertor.convert([array objectAtIndex:index]), 0); + } else { + ASSERT(current.type == ContainerDictionary); + ASSERT([current.objc isKindOfClass:[NSDictionary class]]); + NSDictionary *dictionary = (NSDictionary *)current.objc; + for (id key in [dictionary keyEnumerator]) { + if ([key isKindOfClass:[NSString class]]) { + JSStringRef propertyName = JSStringCreateWithCFString((CFStringRef)key); + JSObjectSetProperty(contextRef, js, propertyName, convertor.convert([dictionary objectForKey:key]), 0, 0); + JSStringRelease(propertyName); + } + } + } + + } while (!convertor.isWorkListEmpty()); + + return task.js; +} + +JSValueRef valueInternalValue(JSValue * value) +{ + return value->m_value; +} + ++ (JSValue *)valueWithJSValueRef:(JSValueRef)value inContext:(JSContext *)context +{ + return [context wrapperForJSObject:value]; +} + +- (JSValue *)init +{ + return nil; +} + +- (JSValue *)initWithValue:(JSValueRef)value inContext:(JSContext *)context +{ + if (!value || !context) + return nil; + + self = [super init]; + if (!self) + return nil; + + _context = [context retain]; + m_value = value; + JSValueProtect([_context JSGlobalContextRef], m_value); + return self; +} + +struct StructTagHandler { + SEL typeToValueSEL; + SEL valueToTypeSEL; +}; +typedef HashMap<String, StructTagHandler> StructHandlers; + +static StructHandlers* createStructHandlerMap() +{ + StructHandlers* structHandlers = new StructHandlers(); + + size_t valueWithXinContextLength = strlen("valueWithX:inContext:"); + size_t toXLength = strlen("toX"); + + // Step 1: find all valueWith<Foo>:inContext: class methods in JSValue. + forEachMethodInClass(object_getClass([JSValue class]), ^(Method method){ + SEL selector = method_getName(method); + const char* name = sel_getName(selector); + size_t nameLength = strlen(name); + // Check for valueWith<Foo>:context: + if (nameLength < valueWithXinContextLength || memcmp(name, "valueWith", 9) || memcmp(name + nameLength - 11, ":inContext:", 11)) + return; + // Check for [ id, SEL, <type>, <contextType> ] + if (method_getNumberOfArguments(method) != 4) + return; + char idType[3]; + // Check 2nd argument type is "@" + char* secondType = method_copyArgumentType(method, 3); + if (strcmp(secondType, "@") != 0) { + free(secondType); + return; + } + free(secondType); + // Check result type is also "@" + method_getReturnType(method, idType, 3); + if (strcmp(idType, "@") != 0) + return; + char* type = method_copyArgumentType(method, 2); + structHandlers->add(StringImpl::create(type), (StructTagHandler){ selector, 0 }); + free(type); + }); + + // Step 2: find all to<Foo> instance methods in JSValue. + forEachMethodInClass([JSValue class], ^(Method method){ + SEL selector = method_getName(method); + const char* name = sel_getName(selector); + size_t nameLength = strlen(name); + // Check for to<Foo> + if (nameLength < toXLength || memcmp(name, "to", 2)) + return; + // Check for [ id, SEL ] + if (method_getNumberOfArguments(method) != 2) + return; + // Try to find a matching valueWith<Foo>:context: method. + char* type = method_copyReturnType(method); + + StructHandlers::iterator iter = structHandlers->find(type); + free(type); + if (iter == structHandlers->end()) + return; + StructTagHandler& handler = iter->value; + + // check that strlen(<foo>) == strlen(<Foo>) + const char* valueWithName = sel_getName(handler.typeToValueSEL); + size_t valueWithLength = strlen(valueWithName); + if (valueWithLength - valueWithXinContextLength != nameLength - toXLength) + return; + // Check that <Foo> == <Foo> + if (memcmp(valueWithName + 9, name + 2, nameLength - toXLength - 1)) + return; + handler.valueToTypeSEL = selector; + }); + + // Step 3: clean up - remove entries where we found prospective valueWith<Foo>:inContext: conversions, but no matching to<Foo> methods. + typedef HashSet<String> RemoveSet; + RemoveSet removeSet; + for (StructHandlers::iterator iter = structHandlers->begin(); iter != structHandlers->end(); ++iter) { + StructTagHandler& handler = iter->value; + if (!handler.valueToTypeSEL) + removeSet.add(iter->key); + } + + for (RemoveSet::iterator iter = removeSet.begin(); iter != removeSet.end(); ++iter) + structHandlers->remove(*iter); + + return structHandlers; +} + +static StructTagHandler* handerForStructTag(const char* encodedType) +{ + static SpinLock handerForStructTagLock = SPINLOCK_INITIALIZER; + SpinLockHolder lockHolder(&handerForStructTagLock); + + static StructHandlers* structHandlers = createStructHandlerMap(); + + StructHandlers::iterator iter = structHandlers->find(encodedType); + if (iter == structHandlers->end()) + return 0; + return &iter->value; +} + ++ (SEL)selectorForStructToValue:(const char *)structTag +{ + StructTagHandler* handler = handerForStructTag(structTag); + return handler ? handler->typeToValueSEL : nil; +} + ++ (SEL)selectorForValueToStruct:(const char *)structTag +{ + StructTagHandler* handler = handerForStructTag(structTag); + return handler ? handler->valueToTypeSEL : nil; +} + +- (void)dealloc +{ + JSValueUnprotect([_context JSGlobalContextRef], m_value); + [_context release]; + _context = nil; + [super dealloc]; +} + +- (NSString *)description +{ + if (id wrapped = tryUnwrapObjcObject([_context JSGlobalContextRef], m_value)) + return [wrapped description]; + return [self toString]; +} + +NSInvocation *typeToValueInvocationFor(const char* encodedType) +{ + SEL selector = [JSValue selectorForStructToValue:encodedType]; + if (!selector) + return 0; + + const char* methodTypes = method_getTypeEncoding(class_getClassMethod([JSValue class], selector)); + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:methodTypes]]; + [invocation setSelector:selector]; + return invocation; +} + +NSInvocation *valueToTypeInvocationFor(const char* encodedType) +{ + SEL selector = [JSValue selectorForValueToStruct:encodedType]; + if (!selector) + return 0; + + const char* methodTypes = method_getTypeEncoding(class_getInstanceMethod([JSValue class], selector)); + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:methodTypes]]; + [invocation setSelector:selector]; + return invocation; +} + +@end + +#endif diff --git a/Source/JavaScriptCore/API/JSValueInternal.h b/Source/JavaScriptCore/API/JSValueInternal.h new file mode 100644 index 000000000..4f1a8f69c --- /dev/null +++ b/Source/JavaScriptCore/API/JSValueInternal.h @@ -0,0 +1,58 @@ +/* + * 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 JSValueInternal_h +#define JSValueInternal_h + +#import <JavaScriptCore/JavaScriptCore.h> +#import <JavaScriptCore/JSValue.h> + +#if JSC_OBJC_API_ENABLED + +@interface JSValue(Internal) + +JSValueRef valueInternalValue(JSValue *); + +- (JSValue *)initWithValue:(JSValueRef)value inContext:(JSContext *)context; + +JSValueRef objectToValue(JSContext *, id); +id valueToObject(JSContext *, JSValueRef); +id valueToNumber(JSGlobalContextRef, JSValueRef, JSValueRef* exception); +id valueToString(JSGlobalContextRef, JSValueRef, JSValueRef* exception); +id valueToDate(JSGlobalContextRef, JSValueRef, JSValueRef* exception); +id valueToArray(JSGlobalContextRef, JSValueRef, JSValueRef* exception); +id valueToDictionary(JSGlobalContextRef, JSValueRef, JSValueRef* exception); + ++ (SEL)selectorForStructToValue:(const char *)structTag; ++ (SEL)selectorForValueToStruct:(const char *)structTag; + +@end + +NSInvocation *typeToValueInvocationFor(const char* encodedType); +NSInvocation *valueToTypeInvocationFor(const char* encodedType); + +#endif + +#endif // JSValueInternal_h diff --git a/Source/JavaScriptCore/API/JSValueRef.cpp b/Source/JavaScriptCore/API/JSValueRef.cpp index 5ff7c03c6..81a2db7b7 100644 --- a/Source/JavaScriptCore/API/JSValueRef.cpp +++ b/Source/JavaScriptCore/API/JSValueRef.cpp @@ -28,15 +28,16 @@ #include "APICast.h" #include "APIShims.h" +#include "JSAPIWrapperObject.h" #include "JSCallbackObject.h" +#include <runtime/JSCJSValue.h> #include <runtime/JSGlobalObject.h> #include <runtime/JSONObject.h> #include <runtime/JSString.h> #include <runtime/LiteralParser.h> #include <runtime/Operations.h> #include <runtime/Protect.h> -#include <runtime/JSValue.h> #include <wtf/Assertions.h> #include <wtf/text/StringHash.h> @@ -44,10 +45,29 @@ #include <algorithm> // for std::min +#if PLATFORM(MAC) +#include <mach-o/dyld.h> +#endif + using namespace JSC; +#if PLATFORM(MAC) +static bool evernoteHackNeeded() +{ + static const int32_t webkitLastVersionWithEvernoteHack = 35133959; + static bool hackNeeded = CFEqual(CFBundleGetIdentifier(CFBundleGetMainBundle()), CFSTR("com.evernote.Evernote")) + && NSVersionOfLinkTimeLibrary("JavaScriptCore") <= webkitLastVersionWithEvernoteHack; + + return hackNeeded; +} +#endif + ::JSType JSValueGetType(JSContextRef ctx, JSValueRef value) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return kJSTypeUndefined; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -69,6 +89,10 @@ using namespace JSC; bool JSValueIsUndefined(JSContextRef ctx, JSValueRef value) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return false; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -78,6 +102,10 @@ bool JSValueIsUndefined(JSContextRef ctx, JSValueRef value) bool JSValueIsNull(JSContextRef ctx, JSValueRef value) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return false; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -87,6 +115,10 @@ bool JSValueIsNull(JSContextRef ctx, JSValueRef value) bool JSValueIsBoolean(JSContextRef ctx, JSValueRef value) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return false; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -96,6 +128,10 @@ bool JSValueIsBoolean(JSContextRef ctx, JSValueRef value) bool JSValueIsNumber(JSContextRef ctx, JSValueRef value) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return false; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -105,6 +141,10 @@ bool JSValueIsNumber(JSContextRef ctx, JSValueRef value) bool JSValueIsString(JSContextRef ctx, JSValueRef value) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return false; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -114,6 +154,10 @@ bool JSValueIsString(JSContextRef ctx, JSValueRef value) bool JSValueIsObject(JSContextRef ctx, JSValueRef value) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return false; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -123,6 +167,10 @@ bool JSValueIsObject(JSContextRef ctx, JSValueRef value) bool JSValueIsObjectOfClass(JSContextRef ctx, JSValueRef value, JSClassRef jsClass) { + if (!ctx || !jsClass) { + ASSERT_NOT_REACHED(); + return false; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -133,12 +181,20 @@ bool JSValueIsObjectOfClass(JSContextRef ctx, JSValueRef value, JSClassRef jsCla return jsCast<JSCallbackObject<JSGlobalObject>*>(o)->inherits(jsClass); if (o->inherits(&JSCallbackObject<JSDestructibleObject>::s_info)) return jsCast<JSCallbackObject<JSDestructibleObject>*>(o)->inherits(jsClass); +#if JSC_OBJC_API_ENABLED + if (o->inherits(&JSCallbackObject<JSAPIWrapperObject>::s_info)) + return jsCast<JSCallbackObject<JSAPIWrapperObject>*>(o)->inherits(jsClass); +#endif } return false; } bool JSValueIsEqual(JSContextRef ctx, JSValueRef a, JSValueRef b, JSValueRef* exception) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return false; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -156,6 +212,10 @@ bool JSValueIsEqual(JSContextRef ctx, JSValueRef a, JSValueRef b, JSValueRef* ex bool JSValueIsStrictEqual(JSContextRef ctx, JSValueRef a, JSValueRef b) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return false; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -167,6 +227,10 @@ bool JSValueIsStrictEqual(JSContextRef ctx, JSValueRef a, JSValueRef b) bool JSValueIsInstanceOfConstructor(JSContextRef ctx, JSValueRef value, JSObjectRef constructor, JSValueRef* exception) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return false; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -186,6 +250,10 @@ bool JSValueIsInstanceOfConstructor(JSContextRef ctx, JSValueRef value, JSObject JSValueRef JSValueMakeUndefined(JSContextRef ctx) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -194,6 +262,10 @@ JSValueRef JSValueMakeUndefined(JSContextRef ctx) JSValueRef JSValueMakeNull(JSContextRef ctx) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -202,6 +274,10 @@ JSValueRef JSValueMakeNull(JSContextRef ctx) JSValueRef JSValueMakeBoolean(JSContextRef ctx, bool value) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -210,13 +286,17 @@ JSValueRef JSValueMakeBoolean(JSContextRef ctx, bool value) JSValueRef JSValueMakeNumber(JSContextRef ctx, double value) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); // Our JSValue representation relies on a standard bit pattern for NaN. NaNs // generated internally to JavaScriptCore naturally have that representation, // but an external NaN might not. - if (isnan(value)) + if (std::isnan(value)) value = QNaN; return toRef(exec, jsNumber(value)); @@ -224,6 +304,10 @@ JSValueRef JSValueMakeNumber(JSContextRef ctx, double value) JSValueRef JSValueMakeString(JSContextRef ctx, JSStringRef string) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -232,6 +316,10 @@ JSValueRef JSValueMakeString(JSContextRef ctx, JSStringRef string) JSValueRef JSValueMakeFromJSONString(JSContextRef ctx, JSStringRef string) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); String str = string->string(); @@ -246,6 +334,10 @@ JSValueRef JSValueMakeFromJSONString(JSContextRef ctx, JSStringRef string) JSStringRef JSValueCreateJSONString(JSContextRef ctx, JSValueRef apiValue, unsigned indent, JSValueRef* exception) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); JSValue value = toJS(exec, apiValue); @@ -263,6 +355,10 @@ JSStringRef JSValueCreateJSONString(JSContextRef ctx, JSValueRef apiValue, unsig bool JSValueToBoolean(JSContextRef ctx, JSValueRef value) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return false; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -272,6 +368,10 @@ bool JSValueToBoolean(JSContextRef ctx, JSValueRef value) double JSValueToNumber(JSContextRef ctx, JSValueRef value, JSValueRef* exception) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return QNaN; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -289,6 +389,10 @@ double JSValueToNumber(JSContextRef ctx, JSValueRef value, JSValueRef* exception JSStringRef JSValueToStringCopy(JSContextRef ctx, JSValueRef value, JSValueRef* exception) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -306,6 +410,10 @@ JSStringRef JSValueToStringCopy(JSContextRef ctx, JSValueRef value, JSValueRef* JSObjectRef JSValueToObject(JSContextRef ctx, JSValueRef value, JSValueRef* exception) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -323,6 +431,10 @@ JSObjectRef JSValueToObject(JSContextRef ctx, JSValueRef value, JSValueRef* exce void JSValueProtect(JSContextRef ctx, JSValueRef value) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); @@ -332,6 +444,11 @@ void JSValueProtect(JSContextRef ctx, JSValueRef value) void JSValueUnprotect(JSContextRef ctx, JSValueRef value) { +#if PLATFORM(MAC) + if ((!value || !ctx) && evernoteHackNeeded()) + return; +#endif + ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); diff --git a/Source/JavaScriptCore/API/JSVirtualMachine.h b/Source/JavaScriptCore/API/JSVirtualMachine.h new file mode 100644 index 000000000..b50616f4c --- /dev/null +++ b/Source/JavaScriptCore/API/JSVirtualMachine.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#import <JavaScriptCore/JavaScriptCore.h> + +#if JSC_OBJC_API_ENABLED + +// An instance of JSVirtualMachine represents a single JavaScript "object space" +// or set of execution resources. Thread safety is supported by locking the +// virtual machine, with concurrent JavaScript execution supported by allocating +// separate instances of JSVirtualMachine. + +NS_CLASS_AVAILABLE(10_9, NA) +@interface JSVirtualMachine : NSObject + +// Create a new JSVirtualMachine. +- (id)init; + +// addManagedReference:withOwner and removeManagedReference:withOwner allow +// clients of JSVirtualMachine to make the JavaScript runtime aware of +// arbitrary external Objective-C object graphs. The runtime can then use +// this information to retain any JavaScript values that are referenced +// from somewhere in said object graph. +// +// For correct behavior clients must make their external object graphs +// reachable from within the JavaScript runtime. If an Objective-C object is +// reachable from within the JavaScript runtime, all managed references +// transitively reachable from it as recorded with +// addManagedReference:withOwner: will be scanned by the garbage collector. +// +- (void)addManagedReference:(id)object withOwner:(id)owner; +- (void)removeManagedReference:(id)object withOwner:(id)owner; + +@end + +#endif diff --git a/Source/JavaScriptCore/API/JSVirtualMachine.mm b/Source/JavaScriptCore/API/JSVirtualMachine.mm new file mode 100644 index 000000000..6bada34a6 --- /dev/null +++ b/Source/JavaScriptCore/API/JSVirtualMachine.mm @@ -0,0 +1,255 @@ +/* + * 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. + */ + +#include "config.h" + +#import "JavaScriptCore.h" + +#if JSC_OBJC_API_ENABLED + +#import "APICast.h" +#import "APIShims.h" +#import "JSVirtualMachine.h" +#import "JSVirtualMachineInternal.h" +#import "JSWrapperMap.h" + +static NSMapTable *globalWrapperCache = 0; + +static Mutex& wrapperCacheLock() +{ + DEFINE_STATIC_LOCAL(Mutex, mutex, ()); + return mutex; +} + +static void initWrapperCache() +{ + ASSERT(!globalWrapperCache); + NSPointerFunctionsOptions keyOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality; + NSPointerFunctionsOptions valueOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality; + globalWrapperCache = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:valueOptions capacity:0]; +} + +static NSMapTable *wrapperCache() +{ + if (!globalWrapperCache) + initWrapperCache(); + return globalWrapperCache; +} + +@interface JSVMWrapperCache : NSObject ++ (void)addWrapper:(JSVirtualMachine *)wrapper forJSContextGroupRef:(JSContextGroupRef)group; ++ (JSVirtualMachine *)wrapperForJSContextGroupRef:(JSContextGroupRef)group; +@end + +@implementation JSVMWrapperCache + ++ (void)addWrapper:(JSVirtualMachine *)wrapper forJSContextGroupRef:(JSContextGroupRef)group +{ + MutexLocker locker(wrapperCacheLock()); + NSMapInsert(wrapperCache(), group, wrapper); +} + ++ (JSVirtualMachine *)wrapperForJSContextGroupRef:(JSContextGroupRef)group +{ + MutexLocker locker(wrapperCacheLock()); + return static_cast<JSVirtualMachine *>(NSMapGet(wrapperCache(), group)); +} + +@end + +@implementation JSVirtualMachine { + JSContextGroupRef m_group; + NSMapTable *m_contextCache; + NSMapTable *m_externalObjectGraph; +} + +- (id)init +{ + JSContextGroupRef group = JSContextGroupCreate(); + self = [self initWithContextGroupRef:group]; + // The extra JSContextGroupRetain is balanced here. + JSContextGroupRelease(group); + return self; +} + +- (id)initWithContextGroupRef:(JSContextGroupRef)group +{ + self = [super init]; + if (!self) + return nil; + + m_group = JSContextGroupRetain(group); + + NSPointerFunctionsOptions keyOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality; + NSPointerFunctionsOptions valueOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality; + m_contextCache = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:valueOptions capacity:0]; + + NSPointerFunctionsOptions weakIDOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality; + NSPointerFunctionsOptions strongIDOptions = NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPersonality; + m_externalObjectGraph = [[NSMapTable alloc] initWithKeyOptions:weakIDOptions valueOptions:strongIDOptions capacity:0]; + + [JSVMWrapperCache addWrapper:self forJSContextGroupRef:group]; + + return self; +} + +- (void)dealloc +{ + JSContextGroupRelease(m_group); + [m_contextCache release]; + [m_externalObjectGraph release]; + [super dealloc]; +} + +static id getInternalObjcObject(id object) +{ + if ([object isKindOfClass:[JSManagedValue class]]) { + JSValue* value = [static_cast<JSManagedValue *>(object) value]; + id temp = tryUnwrapObjcObject([value.context JSGlobalContextRef], [value JSValueRef]); + if (temp) + return temp; + return object; + } + + if ([object isKindOfClass:[JSValue class]]) { + JSValue *value = static_cast<JSValue *>(object); + object = tryUnwrapObjcObject([value.context JSGlobalContextRef], [value JSValueRef]); + } + + return object; +} + +- (void)addManagedReference:(id)object withOwner:(id)owner +{ + object = getInternalObjcObject(object); + owner = getInternalObjcObject(owner); + + if (!object || !owner) + return; + + JSC::APIEntryShim shim(toJS(m_group)); + + NSMapTable *ownedObjects = [m_externalObjectGraph objectForKey:owner]; + if (!ownedObjects) { + NSPointerFunctionsOptions weakIDOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality; + NSPointerFunctionsOptions integerOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsIntegerPersonality; + ownedObjects = [[NSMapTable alloc] initWithKeyOptions:weakIDOptions valueOptions:integerOptions capacity:1]; + + [m_externalObjectGraph setObject:ownedObjects forKey:owner]; + [ownedObjects release]; + } + NSMapInsert(ownedObjects, object, reinterpret_cast<void*>(reinterpret_cast<size_t>(NSMapGet(ownedObjects, object)) + 1)); +} + +- (void)removeManagedReference:(id)object withOwner:(id)owner +{ + object = getInternalObjcObject(object); + owner = getInternalObjcObject(owner); + + if (!object || !owner) + return; + + JSC::APIEntryShim shim(toJS(m_group)); + + NSMapTable *ownedObjects = [m_externalObjectGraph objectForKey:owner]; + if (!ownedObjects) + return; + + size_t count = reinterpret_cast<size_t>(NSMapGet(ownedObjects, object)); + if (count > 1) { + NSMapInsert(ownedObjects, object, reinterpret_cast<void*>(count - 1)); + return; + } + + if (count == 1) + NSMapRemove(ownedObjects, object); + + if (![ownedObjects count]) + [m_externalObjectGraph removeObjectForKey:owner]; +} + +@end + +@implementation JSVirtualMachine(Internal) + +JSContextGroupRef getGroupFromVirtualMachine(JSVirtualMachine *virtualMachine) +{ + return virtualMachine->m_group; +} + ++ (JSVirtualMachine *)virtualMachineWithContextGroupRef:(JSContextGroupRef)group +{ + JSVirtualMachine *virtualMachine = [JSVMWrapperCache wrapperForJSContextGroupRef:group]; + if (!virtualMachine) + virtualMachine = [[[JSVirtualMachine alloc] initWithContextGroupRef:group] autorelease]; + return virtualMachine; +} + +- (JSContext *)contextForGlobalContextRef:(JSGlobalContextRef)globalContext +{ + return static_cast<JSContext *>(NSMapGet(m_contextCache, globalContext)); +} + +- (void)addContext:(JSContext *)wrapper forGlobalContextRef:(JSGlobalContextRef)globalContext +{ + NSMapInsert(m_contextCache, globalContext, wrapper); +} + +- (NSMapTable *)externalObjectGraph +{ + return m_externalObjectGraph; +} + +@end + +void scanExternalObjectGraph(JSC::VM& vm, JSC::SlotVisitor& visitor, void* root) +{ + @autoreleasepool { + JSVirtualMachine *virtualMachine = [JSVMWrapperCache wrapperForJSContextGroupRef:toRef(&vm)]; + if (!virtualMachine) + return; + NSMapTable *externalObjectGraph = [virtualMachine externalObjectGraph]; + Vector<void*> stack; + stack.append(root); + while (!stack.isEmpty()) { + void* nextRoot = stack.last(); + stack.removeLast(); + if (visitor.containsOpaqueRootTriState(nextRoot) == TrueTriState) + continue; + visitor.addOpaqueRoot(nextRoot); + + NSMapTable *ownedObjects = [externalObjectGraph objectForKey:static_cast<id>(nextRoot)]; + id ownedObject; + NSEnumerator *enumerator = [ownedObjects keyEnumerator]; + while ((ownedObject = [enumerator nextObject])) { + ASSERT(reinterpret_cast<size_t>(NSMapGet(ownedObjects, ownedObject)) == 1); + stack.append(static_cast<void*>(ownedObject)); + } + } + } +} + +#endif + diff --git a/Source/JavaScriptCore/API/JSVirtualMachineInternal.h b/Source/JavaScriptCore/API/JSVirtualMachineInternal.h new file mode 100644 index 000000000..729226566 --- /dev/null +++ b/Source/JavaScriptCore/API/JSVirtualMachineInternal.h @@ -0,0 +1,57 @@ +/* + * 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 JSVirtualMachineInternal_h +#define JSVirtualMachineInternal_h + +#import <JavaScriptCore/JavaScriptCore.h> + +#if JSC_OBJC_API_ENABLED + +namespace JSC { +class VM; +class SlotVisitor; +} + +#if defined(__OBJC__) +@interface JSVirtualMachine(Internal) + +JSContextGroupRef getGroupFromVirtualMachine(JSVirtualMachine *); + ++ (JSVirtualMachine *)virtualMachineWithContextGroupRef:(JSContextGroupRef)group; + +- (JSContext *)contextForGlobalContextRef:(JSGlobalContextRef)globalContext; +- (void)addContext:(JSContext *)wrapper forGlobalContextRef:(JSGlobalContextRef)globalContext; + +- (NSMapTable *)externalObjectGraph; + +@end +#endif // defined(__OBJC__) + +void scanExternalObjectGraph(JSC::VM&, JSC::SlotVisitor&, void* root); + +#endif + +#endif // JSVirtualMachineInternal_h diff --git a/Source/JavaScriptCore/API/JSWeakObjectMapRefPrivate.cpp b/Source/JavaScriptCore/API/JSWeakObjectMapRefPrivate.cpp index bdfce08ee..8cbe263a7 100644 --- a/Source/JavaScriptCore/API/JSWeakObjectMapRefPrivate.cpp +++ b/Source/JavaScriptCore/API/JSWeakObjectMapRefPrivate.cpp @@ -28,9 +28,11 @@ #include "APICast.h" #include "APIShims.h" +#include "JSCJSValue.h" #include "JSCallbackObject.h" -#include "JSValue.h" #include "JSWeakObjectMapRefInternal.h" +#include "Operations.h" +#include "Weak.h" #include <wtf/HashMap.h> #include <wtf/text/StringHash.h> @@ -52,17 +54,25 @@ JSWeakObjectMapRef JSWeakObjectMapCreate(JSContextRef context, void* privateData void JSWeakObjectMapSet(JSContextRef ctx, JSWeakObjectMapRef map, void* key, JSObjectRef object) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); JSObject* obj = toJS(object); if (!obj) return; ASSERT(obj->inherits(&JSCallbackObject<JSGlobalObject>::s_info) || obj->inherits(&JSCallbackObject<JSDestructibleObject>::s_info)); - map->map().set(exec->globalData(), key, obj); + map->map().set(key, obj); } JSObjectRef JSWeakObjectMapGet(JSContextRef ctx, JSWeakObjectMapRef map, void* key) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return 0; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); return toRef(jsCast<JSObject*>(map->map().get(key))); @@ -70,6 +80,10 @@ JSObjectRef JSWeakObjectMapGet(JSContextRef ctx, JSWeakObjectMapRef map, void* k void JSWeakObjectMapRemove(JSContextRef ctx, JSWeakObjectMapRef map, void* key) { + if (!ctx) { + ASSERT_NOT_REACHED(); + return; + } ExecState* exec = toJS(ctx); APIEntryShim entryShim(exec); map->map().remove(key); diff --git a/Source/JavaScriptCore/API/JSWrapperMap.h b/Source/JavaScriptCore/API/JSWrapperMap.h new file mode 100644 index 000000000..ce74a9c61 --- /dev/null +++ b/Source/JavaScriptCore/API/JSWrapperMap.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#import <JavaScriptCore/JavaScriptCore.h> +#import <JSValueInternal.h> +#import <objc/objc-runtime.h> + +#if JSC_OBJC_API_ENABLED + +@interface JSWrapperMap : NSObject + +- (id)initWithContext:(JSContext *)context; + +- (JSValue *)jsWrapperForObject:(id)object; + +- (JSValue *)objcWrapperForJSValueRef:(JSValueRef)value; + +@end + +id tryUnwrapObjcObject(JSGlobalContextRef, JSValueRef); + +Protocol *getJSExportProtocol(); +Class getNSBlockClass(); + +#endif diff --git a/Source/JavaScriptCore/API/JSWrapperMap.mm b/Source/JavaScriptCore/API/JSWrapperMap.mm new file mode 100644 index 000000000..4dde1a659 --- /dev/null +++ b/Source/JavaScriptCore/API/JSWrapperMap.mm @@ -0,0 +1,521 @@ +/* + * 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. + */ + +#include "config.h" +#import "JavaScriptCore.h" + +#if JSC_OBJC_API_ENABLED + +#import "APICast.h" +#import "APIShims.h" +#import "JSAPIWrapperObject.h" +#import "JSCallbackObject.h" +#import "JSContextInternal.h" +#import "JSWrapperMap.h" +#import "ObjCCallbackFunction.h" +#import "ObjcRuntimeExtras.h" +#import "Operations.h" +#import "WeakGCMap.h" +#import <wtf/TCSpinLock.h> +#import <wtf/Vector.h> + +@class JSObjCClassInfo; + +@interface JSWrapperMap () + +- (JSObjCClassInfo*)classInfoForClass:(Class)cls; + +@end + +// Default conversion of selectors to property names. +// All semicolons are removed, lowercase letters following a semicolon are capitalized. +static NSString *selectorToPropertyName(const char* start) +{ + // Use 'index' to check for colons, if there are none, this is easy! + const char* firstColon = index(start, ':'); + if (!firstColon) + return [NSString stringWithUTF8String:start]; + + // 'header' is the length of string up to the first colon. + size_t header = firstColon - start; + // The new string needs to be long enough to hold 'header', plus the remainder of the string, excluding + // at least one ':', but including a '\0'. (This is conservative if there are more than one ':'). + char* buffer = static_cast<char*>(malloc(header + strlen(firstColon + 1) + 1)); + // Copy 'header' characters, set output to point to the end of this & input to point past the first ':'. + memcpy(buffer, start, header); + char* output = buffer + header; + const char* input = start + header + 1; + + // On entry to the loop, we have already skipped over a ':' from the input. + while (true) { + char c; + // Skip over any additional ':'s. We'll leave c holding the next character after the + // last ':', and input pointing past c. + while ((c = *(input++)) == ':'); + // Copy the character, converting to upper case if necessary. + // If the character we copy is '\0', then we're done! + if (!(*(output++) = toupper(c))) + goto done; + // Loop over characters other than ':'. + while ((c = *(input++)) != ':') { + // Copy the character. + // If the character we copy is '\0', then we're done! + if (!(*(output++) = c)) + goto done; + } + // If we get here, we've consumed a ':' - wash, rinse, repeat. + } +done: + NSString *result = [NSString stringWithUTF8String:buffer]; + free(buffer); + return result; +} + +static JSObjectRef makeWrapper(JSContextRef ctx, JSClassRef jsClass, id wrappedObject) +{ + JSC::ExecState* exec = toJS(ctx); + JSC::APIEntryShim entryShim(exec); + + ASSERT(jsClass); + JSC::JSCallbackObject<JSC::JSAPIWrapperObject>* object = JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::create(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->objcWrapperObjectStructure(), jsClass, 0); + object->setWrappedObject(wrappedObject); + if (JSC::JSObject* prototype = jsClass->prototype(exec)) + object->setPrototype(exec->vm(), prototype); + + return toRef(object); +} + +// Make an object that is in all ways a completely vanilla JavaScript object, +// other than that it has a native brand set that will be displayed by the default +// Object.prototype.toString conversion. +static JSValue *objectWithCustomBrand(JSContext *context, NSString *brand, Class cls = 0) +{ + JSClassDefinition definition; + definition = kJSClassDefinitionEmpty; + definition.className = [brand UTF8String]; + 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) +{ + NSMutableDictionary *renameMap = [[NSMutableDictionary alloc] init]; + + forEachMethodInProtocol(protocol, NO, isInstanceMethod, ^(SEL sel, const char*){ + NSString *rename = @(sel_getName(sel)); + NSRange range = [rename rangeOfString:@"__JS_EXPORT_AS__"]; + if (range.location == NSNotFound) + return; + NSString *selector = [rename substringToIndex:range.location]; + NSUInteger begin = range.location + range.length; + NSUInteger length = [rename length] - begin - 1; + NSString *name = [rename substringWithRange:(NSRange){ begin, length }]; + renameMap[selector] = name; + }); + + return renameMap; +} + +inline void putNonEnumerable(JSValue *base, NSString *propertyName, JSValue *value) +{ + [base defineProperty:propertyName descriptor:@{ + JSPropertyDescriptorValueKey: value, + JSPropertyDescriptorWritableKey: @YES, + JSPropertyDescriptorEnumerableKey: @NO, + JSPropertyDescriptorConfigurableKey: @YES + }]; +} + +// This method will iterate over the set of required methods in the protocol, and: +// * Determine a property name (either via a renameMap or default conversion). +// * If an accessorMap is provided, and contains this name, store the method in the map. +// * Otherwise, if the object doesn't already contain a property with name, create it. +static void copyMethodsToObject(JSContext *context, Class objcClass, Protocol *protocol, BOOL isInstanceMethod, JSValue *object, NSMutableDictionary *accessorMethods = nil) +{ + NSMutableDictionary *renameMap = createRenameMap(protocol, isInstanceMethod); + + forEachMethodInProtocol(protocol, YES, isInstanceMethod, ^(SEL sel, const char* types){ + const char* nameCStr = sel_getName(sel); + NSString *name = @(nameCStr); + if (accessorMethods && accessorMethods[name]) { + JSObjectRef method = objCCallbackFunctionForMethod(context, objcClass, protocol, isInstanceMethod, sel, types); + if (!method) + return; + accessorMethods[name] = [JSValue valueWithJSValueRef:method inContext:context]; + } else { + name = renameMap[name]; + if (!name) + name = selectorToPropertyName(nameCStr); + if ([object hasProperty:name]) + return; + JSObjectRef method = objCCallbackFunctionForMethod(context, objcClass, protocol, isInstanceMethod, sel, types); + if (method) + putNonEnumerable(object, name, [JSValue valueWithJSValueRef:method inContext:context]); + } + }); + + [renameMap release]; +} + +static bool parsePropertyAttributes(objc_property_t property, char*& getterName, char*& setterName) +{ + bool readonly = false; + unsigned attributeCount; + objc_property_attribute_t* attributes = property_copyAttributeList(property, &attributeCount); + if (attributeCount) { + for (unsigned i = 0; i < attributeCount; ++i) { + switch (*(attributes[i].name)) { + case 'G': + getterName = strdup(attributes[i].value); + break; + case 'S': + setterName = strdup(attributes[i].value); + break; + case 'R': + readonly = true; + break; + default: + break; + } + } + free(attributes); + } + return readonly; +} + +static char* makeSetterName(const char* name) +{ + size_t nameLength = strlen(name); + char* setterName = (char*)malloc(nameLength + 5); // "set" Name ":\0" + setterName[0] = 's'; + setterName[1] = 'e'; + setterName[2] = 't'; + setterName[3] = toupper(*name); + memcpy(setterName + 4, name + 1, nameLength - 1); + setterName[nameLength + 3] = ':'; + setterName[nameLength + 4] = '\0'; + return setterName; +} + +static void copyPrototypeProperties(JSContext *context, Class objcClass, Protocol *protocol, JSValue *prototypeValue) +{ + // First gather propreties into this list, then handle the methods (capturing the accessor methods). + struct Property { + const char* name; + char* getterName; + char* setterName; + }; + __block Vector<Property> propertyList; + + // Map recording the methods used as getters/setters. + NSMutableDictionary *accessorMethods = [NSMutableDictionary dictionary]; + + // Useful value. + JSValue *undefined = [JSValue valueWithUndefinedInContext:context]; + + forEachPropertyInProtocol(protocol, ^(objc_property_t property){ + char* getterName = 0; + char* setterName = 0; + bool readonly = parsePropertyAttributes(property, getterName, setterName); + const char* name = property_getName(property); + + // Add the names of the getter & setter methods to + if (!getterName) + getterName = strdup(name); + accessorMethods[@(getterName)] = undefined; + if (!readonly) { + if (!setterName) + setterName = makeSetterName(name); + accessorMethods[@(setterName)] = undefined; + } + + // Add the properties to a list. + propertyList.append((Property){ name, getterName, setterName }); + }); + + // Copy methods to the prototype, capturing accessors in the accessorMethods map. + copyMethodsToObject(context, objcClass, protocol, YES, prototypeValue, accessorMethods); + + // Iterate the propertyList & generate accessor properties. + for (size_t i = 0; i < propertyList.size(); ++i) { + Property& property = propertyList[i]; + + JSValue *getter = accessorMethods[@(property.getterName)]; + free(property.getterName); + ASSERT(![getter isUndefined]); + + JSValue *setter = undefined; + if (property.setterName) { + setter = accessorMethods[@(property.setterName)]; + free(property.setterName); + ASSERT(![setter isUndefined]); + } + + [prototypeValue defineProperty:@(property.name) descriptor:@{ + JSPropertyDescriptorGetKey: getter, + JSPropertyDescriptorSetKey: setter, + JSPropertyDescriptorEnumerableKey: @NO, + JSPropertyDescriptorConfigurableKey: @YES + }]; + } +} + +@interface JSObjCClassInfo : NSObject { + JSContext *m_context; + Class m_class; + bool m_block; + JSClassRef m_classRef; + JSC::Weak<JSC::JSObject> m_prototype; + JSC::Weak<JSC::JSObject> m_constructor; +} + +- (id)initWithContext:(JSContext *)context forClass:(Class)cls superClassInfo:(JSObjCClassInfo*)superClassInfo; +- (JSValue *)wrapperForObject:(id)object; +- (JSValue *)constructor; + +@end + +@implementation JSObjCClassInfo + +- (id)initWithContext:(JSContext *)context forClass:(Class)cls superClassInfo:(JSObjCClassInfo*)superClassInfo +{ + self = [super init]; + if (!self) + return nil; + + const char* className = class_getName(cls); + m_context = context; + m_class = cls; + m_block = [cls isSubclassOfClass:getNSBlockClass()]; + JSClassDefinition definition; + definition = kJSClassDefinitionEmpty; + definition.className = className; + m_classRef = JSClassCreate(&definition); + + [self allocateConstructorAndPrototypeWithSuperClassInfo:superClassInfo]; + + return self; +} + +- (void)dealloc +{ + JSClassRelease(m_classRef); + [super dealloc]; +} + +- (void)allocateConstructorAndPrototypeWithSuperClassInfo:(JSObjCClassInfo*)superClassInfo +{ + ASSERT(!m_constructor || !m_prototype); + ASSERT((m_class == [NSObject class]) == !superClassInfo); + if (!superClassInfo) { + JSContextRef cContext = [m_context JSGlobalContextRef]; + JSValue *constructor = m_context[@"Object"]; + if (!m_constructor) + m_constructor = toJS(JSValueToObject(cContext, valueInternalValue(constructor), 0)); + + if (!m_prototype) { + JSValue *prototype = constructor[@"prototype"]; + m_prototype = toJS(JSValueToObject(cContext, valueInternalValue(prototype), 0)); + } + } else { + const char* className = class_getName(m_class); + + // Create or grab the prototype/constructor pair. + JSValue *prototype; + JSValue *constructor; + if (m_prototype) + prototype = [JSValue valueWithJSValueRef:toRef(m_prototype.get()) inContext:m_context]; + else + prototype = objectWithCustomBrand(m_context, [NSString stringWithFormat:@"%sPrototype", className]); + + if (m_constructor) + constructor = [JSValue valueWithJSValueRef:toRef(m_constructor.get()) inContext:m_context]; + else + constructor = objectWithCustomBrand(m_context, [NSString stringWithFormat:@"%sConstructor", className], m_class); + + JSContextRef cContext = [m_context JSGlobalContextRef]; + m_prototype = toJS(JSValueToObject(cContext, valueInternalValue(prototype), 0)); + m_constructor = toJS(JSValueToObject(cContext, valueInternalValue(constructor), 0)); + + putNonEnumerable(prototype, @"constructor", constructor); + putNonEnumerable(constructor, @"prototype", prototype); + + Protocol *exportProtocol = getJSExportProtocol(); + forEachProtocolImplementingProtocol(m_class, exportProtocol, ^(Protocol *protocol){ + copyPrototypeProperties(m_context, m_class, protocol, prototype); + copyMethodsToObject(m_context, m_class, protocol, NO, constructor); + }); + + // Set [Prototype]. + JSObjectSetPrototype([m_context JSGlobalContextRef], toRef(m_prototype.get()), toRef(superClassInfo->m_prototype.get())); + } +} + +- (void)reallocateConstructorAndOrPrototype +{ + [self allocateConstructorAndPrototypeWithSuperClassInfo:[m_context.wrapperMap classInfoForClass:class_getSuperclass(m_class)]]; +} + +- (JSValue *)wrapperForObject:(id)object +{ + 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 (!m_prototype) + [self reallocateConstructorAndOrPrototype]; + ASSERT(!!m_prototype); + + JSObjectRef wrapper = makeWrapper([m_context JSGlobalContextRef], m_classRef, object); + JSObjectSetPrototype([m_context JSGlobalContextRef], wrapper, toRef(m_prototype.get())); + return [JSValue valueWithJSValueRef:wrapper inContext:m_context]; +} + +- (JSValue *)constructor +{ + if (!m_constructor) + [self reallocateConstructorAndOrPrototype]; + ASSERT(!!m_constructor); + return [JSValue valueWithJSValueRef:toRef(m_constructor.get()) inContext:m_context]; +} + +@end + +@implementation JSWrapperMap { + JSContext *m_context; + NSMutableDictionary *m_classMap; + JSC::WeakGCMap<id, JSC::JSObject> m_cachedJSWrappers; + NSMapTable *m_cachedObjCWrappers; +} + +- (id)initWithContext:(JSContext *)context +{ + self = [super init]; + if (!self) + return nil; + + NSPointerFunctionsOptions keyOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality; + NSPointerFunctionsOptions valueOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality; + m_cachedObjCWrappers = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:valueOptions capacity:0]; + + m_context = context; + m_classMap = [[NSMutableDictionary alloc] init]; + return self; +} + +- (void)dealloc +{ + [m_cachedObjCWrappers release]; + [m_classMap release]; + [super dealloc]; +} + +- (JSObjCClassInfo*)classInfoForClass:(Class)cls +{ + if (!cls) + return nil; + + // Check if we've already created a JSObjCClassInfo for this Class. + if (JSObjCClassInfo* classInfo = (JSObjCClassInfo*)m_classMap[cls]) + return classInfo; + + // Skip internal classes beginning with '_' - just copy link to the parent class's info. + if ('_' == *class_getName(cls)) + return m_classMap[cls] = [self classInfoForClass:class_getSuperclass(cls)]; + + return m_classMap[cls] = [[[JSObjCClassInfo alloc] initWithContext:m_context forClass:cls superClassInfo:[self classInfoForClass:class_getSuperclass(cls)]] autorelease]; +} + +- (JSValue *)jsWrapperForObject:(id)object +{ + JSC::JSObject* jsWrapper = m_cachedJSWrappers.get(object); + if (jsWrapper) + return [JSValue valueWithJSValueRef:toRef(jsWrapper) inContext:m_context]; + + JSValue *wrapper; + if (class_isMetaClass(object_getClass(object))) + wrapper = [[self classInfoForClass:(Class)object] constructor]; + else { + JSObjCClassInfo* classInfo = [self classInfoForClass:[object class]]; + wrapper = [classInfo wrapperForObject:object]; + } + + // FIXME: https://bugs.webkit.org/show_bug.cgi?id=105891 + // This general approach to wrapper caching is pretty effective, but there are a couple of problems: + // (1) For immortal objects JSValues will effectively leak and this results in error output being logged - we should avoid adding associated objects to immortal objects. + // (2) A long lived object may rack up many JSValues. When the contexts are released these will unprotect the associated JavaScript objects, + // but still, would probably nicer if we made it so that only one associated object was required, broadcasting object dealloc. + JSC::ExecState* exec = toJS([m_context JSGlobalContextRef]); + jsWrapper = toJS(exec, valueInternalValue(wrapper)).toObject(exec); + m_cachedJSWrappers.set(object, jsWrapper); + return wrapper; +} + +- (JSValue *)objcWrapperForJSValueRef:(JSValueRef)value +{ + JSValue *wrapper = static_cast<JSValue *>(NSMapGet(m_cachedObjCWrappers, value)); + if (!wrapper) { + wrapper = [[[JSValue alloc] initWithValue:value inContext:m_context] autorelease]; + NSMapInsert(m_cachedObjCWrappers, value, wrapper); + } + return wrapper; +} + +@end + +id tryUnwrapObjcObject(JSGlobalContextRef context, JSValueRef value) +{ + if (!JSValueIsObject(context, value)) + return nil; + JSValueRef exception = 0; + JSObjectRef object = JSValueToObject(context, value, &exception); + 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)) + return target; + return nil; +} + +Protocol *getJSExportProtocol() +{ + static Protocol *protocol = objc_getProtocol("JSExport"); + return protocol; +} + +Class getNSBlockClass() +{ + static Class cls = objc_getClass("NSBlock"); + return cls; +} + +#endif diff --git a/Source/JavaScriptCore/API/JavaScriptCore.h b/Source/JavaScriptCore/API/JavaScriptCore.h index 87d601851..40bea9c3d 100644 --- a/Source/JavaScriptCore/API/JavaScriptCore.h +++ b/Source/JavaScriptCore/API/JavaScriptCore.h @@ -29,4 +29,14 @@ #include <JavaScriptCore/JavaScript.h> #include <JavaScriptCore/JSStringRefCF.h> +#if defined(__OBJC__) && JSC_OBJC_API_ENABLED + +#import "JSContext.h" +#import "JSValue.h" +#import "JSManagedValue.h" +#import "JSVirtualMachine.h" +#import "JSExport.h" + +#endif + #endif /* JavaScriptCore_h */ diff --git a/Source/JavaScriptCore/API/ObjCCallbackFunction.h b/Source/JavaScriptCore/API/ObjCCallbackFunction.h new file mode 100644 index 000000000..0218cd8b4 --- /dev/null +++ b/Source/JavaScriptCore/API/ObjCCallbackFunction.h @@ -0,0 +1,73 @@ +/* + * 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 ObjCCallbackFunction_h +#define ObjCCallbackFunction_h + +#include <JavaScriptCore/JSBase.h> + +#if JSC_OBJC_API_ENABLED + +#import <JavaScriptCore/JSCallbackFunction.h> + +#if defined(__OBJC__) +JSObjectRef objCCallbackFunctionForMethod(JSContext *, Class, Protocol *, BOOL isInstanceMethod, SEL, const char* types); +JSObjectRef objCCallbackFunctionForBlock(JSContext *, id); + +id tryUnwrapBlock(JSObjectRef); +#endif + +namespace JSC { + +class ObjCCallbackFunctionImpl; + +class ObjCCallbackFunction : public JSCallbackFunction { +public: + typedef JSCallbackFunction Base; + + static ObjCCallbackFunction* create(ExecState*, JSGlobalObject*, const String& name, PassOwnPtr<ObjCCallbackFunctionImpl>); + static void destroy(JSCell*); + + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) + { + ASSERT(globalObject); + return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), &s_info); + } + + static JS_EXPORTDATA const ClassInfo s_info; + + ObjCCallbackFunctionImpl* impl() { return m_impl.get(); } + +protected: + ObjCCallbackFunction(JSGlobalObject*, JSObjectCallAsFunctionCallback, PassOwnPtr<ObjCCallbackFunctionImpl>); + +private: + OwnPtr<ObjCCallbackFunctionImpl> m_impl; +}; + +} // namespace JSC + +#endif + +#endif // ObjCCallbackFunction_h diff --git a/Source/JavaScriptCore/API/ObjCCallbackFunction.mm b/Source/JavaScriptCore/API/ObjCCallbackFunction.mm new file mode 100644 index 000000000..cc342f59e --- /dev/null +++ b/Source/JavaScriptCore/API/ObjCCallbackFunction.mm @@ -0,0 +1,615 @@ +/* + * 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. + */ + +#include "config.h" +#import "JavaScriptCore.h" + +#if JSC_OBJC_API_ENABLED + +#import "APICast.h" +#import "APIShims.h" +#import "Error.h" +#import "JSCJSValueInlines.h" +#import "JSCell.h" +#import "JSCellInlines.h" +#import "JSContextInternal.h" +#import "JSWrapperMap.h" +#import "JSValueInternal.h" +#import "ObjCCallbackFunction.h" +#import "ObjcRuntimeExtras.h" +#import <objc/runtime.h> +#import <wtf/RetainPtr.h> + +class CallbackArgument { +public: + virtual ~CallbackArgument(); + virtual void set(NSInvocation *, NSInteger, JSContext *, JSValueRef, JSValueRef*) = 0; + + OwnPtr<CallbackArgument> m_next; +}; + +CallbackArgument::~CallbackArgument() +{ +} + +class CallbackArgumentBoolean : public CallbackArgument { + virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef*) override + { + bool value = JSValueToBoolean([context JSGlobalContextRef], argument); + [invocation setArgument:&value atIndex:argumentNumber]; + } +}; + +template<typename T> +class CallbackArgumentInteger : public CallbackArgument { + virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) override + { + T value = (T)JSC::toInt32(JSValueToNumber([context JSGlobalContextRef], argument, exception)); + [invocation setArgument:&value atIndex:argumentNumber]; + } +}; + +template<typename T> +class CallbackArgumentDouble : public CallbackArgument { + virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) override + { + T value = (T)JSValueToNumber([context JSGlobalContextRef], argument, exception); + [invocation setArgument:&value atIndex:argumentNumber]; + } +}; + +class CallbackArgumentJSValue : public CallbackArgument { + virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef*) override + { + JSValue *value = [JSValue valueWithJSValueRef:argument inContext:context]; + [invocation setArgument:&value atIndex:argumentNumber]; + } +}; + +class CallbackArgumentId : public CallbackArgument { + virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef*) override + { + id value = valueToObject(context, argument); + [invocation setArgument:&value atIndex:argumentNumber]; + } +}; + +class CallbackArgumentOfClass : public CallbackArgument { +public: + CallbackArgumentOfClass(Class cls) + : CallbackArgument() + , m_class(cls) + { + [m_class retain]; + } + +private: + virtual ~CallbackArgumentOfClass() + { + [m_class release]; + } + + virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) override + { + JSGlobalContextRef contextRef = [context JSGlobalContextRef]; + + id object = tryUnwrapObjcObject(contextRef, argument); + if (object && [object isKindOfClass:m_class]) { + [invocation setArgument:&object atIndex:argumentNumber]; + return; + } + + if (JSValueIsNull(contextRef, argument) || JSValueIsUndefined(contextRef, argument)) { + object = nil; + [invocation setArgument:&object atIndex:argumentNumber]; + return; + } + + *exception = toRef(JSC::createTypeError(toJS(contextRef), "Argument does not match Objective-C Class")); + } + + Class m_class; +}; + +class CallbackArgumentNSNumber : public CallbackArgument { + virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) override + { + id value = valueToNumber([context JSGlobalContextRef], argument, exception); + [invocation setArgument:&value atIndex:argumentNumber]; + } +}; + +class CallbackArgumentNSString : public CallbackArgument { + virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) override + { + id value = valueToString([context JSGlobalContextRef], argument, exception); + [invocation setArgument:&value atIndex:argumentNumber]; + } +}; + +class CallbackArgumentNSDate : public CallbackArgument { + virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) override + { + id value = valueToDate([context JSGlobalContextRef], argument, exception); + [invocation setArgument:&value atIndex:argumentNumber]; + } +}; + +class CallbackArgumentNSArray : public CallbackArgument { + virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) override + { + id value = valueToArray([context JSGlobalContextRef], argument, exception); + [invocation setArgument:&value atIndex:argumentNumber]; + } +}; + +class CallbackArgumentNSDictionary : public CallbackArgument { + virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef* exception) override + { + id value = valueToDictionary([context JSGlobalContextRef], argument, exception); + [invocation setArgument:&value atIndex:argumentNumber]; + } +}; + +class CallbackArgumentStruct : public CallbackArgument { +public: + CallbackArgumentStruct(NSInvocation *conversionInvocation, const char* encodedType) + : m_conversionInvocation(conversionInvocation) + , m_buffer(encodedType) + { + } + +private: + virtual void set(NSInvocation *invocation, NSInteger argumentNumber, JSContext *context, JSValueRef argument, JSValueRef*) override + { + JSValue *value = [JSValue valueWithJSValueRef:argument inContext:context]; + [m_conversionInvocation invokeWithTarget:value]; + [m_conversionInvocation getReturnValue:m_buffer]; + [invocation setArgument:m_buffer atIndex:argumentNumber]; + } + + RetainPtr<NSInvocation> m_conversionInvocation; + StructBuffer m_buffer; +}; + +class ArgumentTypeDelegate { +public: + typedef CallbackArgument* ResultType; + + template<typename T> + static ResultType typeInteger() + { + return new CallbackArgumentInteger<T>; + } + + template<typename T> + static ResultType typeDouble() + { + return new CallbackArgumentDouble<T>; + } + + static ResultType typeBool() + { + return new CallbackArgumentBoolean; + } + + static ResultType typeVoid() + { + RELEASE_ASSERT_NOT_REACHED(); + return 0; + } + + static ResultType typeId() + { + return new CallbackArgumentId; + } + + static ResultType typeOfClass(const char* begin, const char* end) + { + StringRange copy(begin, end); + Class cls = objc_getClass(copy); + if (!cls) + return 0; + + if (cls == [JSValue class]) + return new CallbackArgumentJSValue; + if (cls == [NSString class]) + return new CallbackArgumentNSString; + if (cls == [NSNumber class]) + return new CallbackArgumentNSNumber; + if (cls == [NSDate class]) + return new CallbackArgumentNSDate; + if (cls == [NSArray class]) + return new CallbackArgumentNSArray; + if (cls == [NSDictionary class]) + return new CallbackArgumentNSDictionary; + + return new CallbackArgumentOfClass(cls); + } + + static ResultType typeBlock(const char*, const char*) + { + return nil; + } + + static ResultType typeStruct(const char* begin, const char* end) + { + StringRange copy(begin, end); + if (NSInvocation *invocation = valueToTypeInvocationFor(copy)) + return new CallbackArgumentStruct(invocation, copy); + return 0; + } +}; + +class CallbackResult { +public: + virtual ~CallbackResult() + { + } + + virtual JSValueRef get(NSInvocation *, JSContext *, JSValueRef*) = 0; +}; + +class CallbackResultVoid : public CallbackResult { + virtual JSValueRef get(NSInvocation *, JSContext *context, JSValueRef*) override + { + return JSValueMakeUndefined([context JSGlobalContextRef]); + } +}; + +class CallbackResultId : public CallbackResult { + virtual JSValueRef get(NSInvocation *invocation, JSContext *context, JSValueRef*) override + { + id value; + [invocation getReturnValue:&value]; + return objectToValue(context, value); + } +}; + +template<typename T> +class CallbackResultNumeric : public CallbackResult { + virtual JSValueRef get(NSInvocation *invocation, JSContext *context, JSValueRef*) override + { + T value; + [invocation getReturnValue:&value]; + return JSValueMakeNumber([context JSGlobalContextRef], value); + } +}; + +class CallbackResultBoolean : public CallbackResult { + virtual JSValueRef get(NSInvocation *invocation, JSContext *context, JSValueRef*) override + { + bool value; + [invocation getReturnValue:&value]; + return JSValueMakeBoolean([context JSGlobalContextRef], value); + } +}; + +class CallbackResultStruct : public CallbackResult { +public: + CallbackResultStruct(NSInvocation *conversionInvocation, const char* encodedType) + : m_conversionInvocation(conversionInvocation) + , m_buffer(encodedType) + { + } + +private: + virtual JSValueRef get(NSInvocation *invocation, JSContext *context, JSValueRef*) override + { + [invocation getReturnValue:m_buffer]; + + [m_conversionInvocation setArgument:m_buffer atIndex:2]; + [m_conversionInvocation setArgument:&context atIndex:3]; + [m_conversionInvocation invokeWithTarget:[JSValue class]]; + + JSValue *value; + [m_conversionInvocation getReturnValue:&value]; + return valueInternalValue(value); + } + + RetainPtr<NSInvocation> m_conversionInvocation; + StructBuffer m_buffer; +}; + +class ResultTypeDelegate { +public: + typedef CallbackResult* ResultType; + + template<typename T> + static ResultType typeInteger() + { + return new CallbackResultNumeric<T>; + } + + template<typename T> + static ResultType typeDouble() + { + return new CallbackResultNumeric<T>; + } + + static ResultType typeBool() + { + return new CallbackResultBoolean; + } + + static ResultType typeVoid() + { + return new CallbackResultVoid; + } + + static ResultType typeId() + { + return new CallbackResultId(); + } + + static ResultType typeOfClass(const char*, const char*) + { + return new CallbackResultId(); + } + + static ResultType typeBlock(const char*, const char*) + { + return new CallbackResultId(); + } + + static ResultType typeStruct(const char* begin, const char* end) + { + StringRange copy(begin, end); + if (NSInvocation *invocation = typeToValueInvocationFor(copy)) + return new CallbackResultStruct(invocation, copy); + return 0; + } +}; + +enum CallbackType { + CallbackInstanceMethod, + CallbackClassMethod, + CallbackBlock +}; + +namespace JSC { + +class ObjCCallbackFunctionImpl { +public: + ObjCCallbackFunctionImpl(JSContext *context, NSInvocation *invocation, CallbackType type, Class instanceClass, PassOwnPtr<CallbackArgument> arguments, PassOwnPtr<CallbackResult> result) + : m_context(context) + , m_type(type) + , m_instanceClass([instanceClass retain]) + , m_invocation(invocation) + , m_arguments(arguments) + , m_result(result) + { + ASSERT(type != CallbackInstanceMethod || instanceClass); + } + + ~ObjCCallbackFunctionImpl() + { + if (m_type != CallbackInstanceMethod) + [[m_invocation.get() target] release]; + [m_instanceClass release]; + } + + JSValueRef call(JSContext *context, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); + + JSContext *context() + { + return m_context.get(); + } + + void setContext(JSContext *context) + { + ASSERT(!m_context.get()); + m_context.set(context); + } + + id wrappedBlock() + { + return m_type == CallbackBlock ? [m_invocation target] : nil; + } + +private: + WeakContextRef m_context; + CallbackType m_type; + Class m_instanceClass; + RetainPtr<NSInvocation> m_invocation; + OwnPtr<CallbackArgument> m_arguments; + OwnPtr<CallbackResult> m_result; +}; + +static JSValueRef objCCallbackFunctionCallAsFunction(JSContextRef callerContext, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + // Retake the API lock - we need this for a few reasons: + // (1) We don't want to support the C-API's confusing drops-locks-once policy - should only drop locks if we can do so recursively. + // (2) We're calling some JSC internals that require us to be on the 'inside' - e.g. createTypeError. + // (3) We need to be locked (per context would be fine) against conflicting usage of the ObjCCallbackFunction's NSInvocation. + JSC::APIEntryShim entryShim(toJS(callerContext)); + + ObjCCallbackFunction* callback = static_cast<ObjCCallbackFunction*>(toJS(function)); + ObjCCallbackFunctionImpl* impl = callback->impl(); + JSContext *context = impl->context(); + if (!context) { + context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(toJS(callerContext)->lexicalGlobalObject()->globalExec())]; + impl->setContext(context); + } + + CallbackData callbackData; + JSValueRef result; + @autoreleasepool { + [context beginCallbackWithData:&callbackData thisValue:thisObject argumentCount:argumentCount arguments:arguments]; + result = impl->call(context, thisObject, argumentCount, arguments, exception); + if (context.exception) + *exception = valueInternalValue(context.exception); + [context endCallbackWithData:&callbackData]; + } + return 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) + , m_impl(impl) +{ +} + +ObjCCallbackFunction* ObjCCallbackFunction::create(JSC::ExecState* exec, 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); + return function; +} + +void ObjCCallbackFunction::destroy(JSCell* cell) +{ + static_cast<ObjCCallbackFunction*>(cell)->ObjCCallbackFunction::~ObjCCallbackFunction(); +} + +JSValueRef ObjCCallbackFunctionImpl::call(JSContext *context, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + JSGlobalContextRef contextRef = [context JSGlobalContextRef]; + + size_t firstArgument; + switch (m_type) { + case CallbackInstanceMethod: { + id 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]; + } + // fallthrough - firstArgument for CallbackInstanceMethod is also 2! + case CallbackClassMethod: + firstArgument = 2; + break; + case CallbackBlock: + firstArgument = 1; + } + + size_t argumentNumber = 0; + for (CallbackArgument* argument = m_arguments.get(); argument; argument = argument->m_next.get()) { + JSValueRef value = argumentNumber < argumentCount ? arguments[argumentNumber] : JSValueMakeUndefined(contextRef); + argument->set(m_invocation.get(), argumentNumber + firstArgument, context, value, exception); + if (*exception) + return JSValueMakeUndefined(contextRef); + ++argumentNumber; + } + + [m_invocation invoke]; + + return m_result->get(m_invocation.get(), context, exception); +} + +} // namespace JSC + +static bool blockSignatureContainsClass() +{ + static bool containsClass = ^{ + id block = ^(NSString *string){ return string; }; + return _Block_has_signature(block) && strstr(_Block_signature(block), "NSString"); + }(); + return containsClass; +} + +inline bool skipNumber(const char*& position) +{ + if (!isASCIIDigit(*position)) + return false; + while (isASCIIDigit(*++position)) { } + return true; +} + +static JSObjectRef objCCallbackFunctionForInvocation(JSContext *context, NSInvocation *invocation, CallbackType type, Class instanceClass, const char* signatureWithObjcClasses) +{ + const char* position = signatureWithObjcClasses; + + OwnPtr<CallbackResult> result = adoptPtr(parseObjCType<ResultTypeDelegate>(position)); + if (!result || !skipNumber(position)) + return nil; + + switch (type) { + case CallbackInstanceMethod: + case CallbackClassMethod: + // Methods are passed two implicit arguments - (id)self, and the selector. + if ('@' != *position++ || !skipNumber(position) || ':' != *position++ || !skipNumber(position)) + return nil; + break; + case CallbackBlock: + // Blocks are passed one implicit argument - the block, of type "@?". + if (('@' != *position++) || ('?' != *position++) || !skipNumber(position)) + return nil; + // Only allow arguments of type 'id' if the block signature contains the NS type information. + if ((!blockSignatureContainsClass() && strchr(position, '@'))) + return nil; + break; + } + + OwnPtr<CallbackArgument> arguments = 0; + OwnPtr<CallbackArgument>* nextArgument = &arguments; + unsigned argumentCount = 0; + while (*position) { + OwnPtr<CallbackArgument> argument = adoptPtr(parseObjCType<ArgumentTypeDelegate>(position)); + if (!argument || !skipNumber(position)) + return nil; + + *nextArgument = argument.release(); + nextArgument = &(*nextArgument)->m_next; + ++argumentCount; + } + + 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())); +} + +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]; + if (!isInstanceMethod) + [invocation setTarget:cls]; + return objCCallbackFunctionForInvocation(context, invocation, isInstanceMethod ? CallbackInstanceMethod : CallbackClassMethod, isInstanceMethod ? cls : nil, _protocol_getMethodTypeEncoding(protocol, sel, YES, isInstanceMethod)); +} + +JSObjectRef objCCallbackFunctionForBlock(JSContext *context, id target) +{ + if (!_Block_has_signature(target)) + return 0; + const char* signature = _Block_signature(target); + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:signature]]; + [invocation setTarget:[target copy]]; + return objCCallbackFunctionForInvocation(context, invocation, CallbackBlock, nil, signature); +} + +id tryUnwrapBlock(JSObjectRef object) +{ + if (!toJS(object)->inherits(&JSC::ObjCCallbackFunction::s_info)) + return nil; + return static_cast<JSC::ObjCCallbackFunction*>(toJS(object))->impl()->wrappedBlock(); +} + +#endif diff --git a/Source/JavaScriptCore/API/ObjcRuntimeExtras.h b/Source/JavaScriptCore/API/ObjcRuntimeExtras.h new file mode 100644 index 000000000..48c112093 --- /dev/null +++ b/Source/JavaScriptCore/API/ObjcRuntimeExtras.h @@ -0,0 +1,231 @@ +/* + * 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. + */ + +#import <objc/runtime.h> +#import <wtf/HashSet.h> +#import <wtf/Vector.h> + +inline bool protocolImplementsProtocol(Protocol *candidate, Protocol *target) +{ + unsigned protocolProtocolsCount; + Protocol ** protocolProtocols = protocol_copyProtocolList(candidate, &protocolProtocolsCount); + for (unsigned i = 0; i < protocolProtocolsCount; ++i) { + if (protocol_isEqual(protocolProtocols[i], target)) { + free(protocolProtocols); + return true; + } + } + free(protocolProtocols); + return false; +} + +inline void forEachProtocolImplementingProtocol(Class cls, Protocol *target, void (^callback)(Protocol *)) +{ + ASSERT(cls); + ASSERT(target); + + Vector<Protocol *> worklist; + HashSet<void*> visited; + + // Initially fill the worklist with the Class's protocols. + unsigned protocolsCount; + Protocol ** protocols = class_copyProtocolList(cls, &protocolsCount); + worklist.append(protocols, protocolsCount); + free(protocols); + + while (!worklist.isEmpty()) { + Protocol *protocol = worklist.last(); + worklist.removeLast(); + + // Are we encountering this Protocol for the first time? + if (!visited.add(protocol).isNewEntry) + continue; + + // If it implements the protocol, make the callback. + if (protocolImplementsProtocol(protocol, target)) + callback(protocol); + + // Add incorporated protocols to the worklist. + protocols = protocol_copyProtocolList(protocol, &protocolsCount); + worklist.append(protocols, protocolsCount); + free(protocols); + } +} + +inline void forEachMethodInClass(Class cls, void (^callback)(Method)) +{ + unsigned count; + Method* methods = class_copyMethodList(cls, &count); + for (unsigned i = 0; i < count; ++i) + callback(methods[i]); + free(methods); +} + +inline void forEachMethodInProtocol(Protocol *protocol, BOOL isRequiredMethod, BOOL isInstanceMethod, void (^callback)(SEL, const char*)) +{ + unsigned count; + struct objc_method_description* methods = protocol_copyMethodDescriptionList(protocol, isRequiredMethod, isInstanceMethod, &count); + for (unsigned i = 0; i < count; ++i) + callback(methods[i].name, methods[i].types); + free(methods); +} + +inline void forEachPropertyInProtocol(Protocol *protocol, void (^callback)(objc_property_t)) +{ + unsigned count; + objc_property_t* properties = protocol_copyPropertyList(protocol, &count); + for (unsigned i = 0; i < count; ++i) + callback(properties[i]); + free(properties); +} + +template<char open, char close> +void skipPair(const char*& position) +{ + size_t count = 1; + do { + char c = *position++; + if (!c) + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Malformed type encoding" userInfo:nil]; + if (c == open) + ++count; + else if (c == close) + --count; + } while (count); +} + +class StringRange { + WTF_MAKE_NONCOPYABLE(StringRange); +public: + StringRange(const char* begin, const char* end) : m_ptr(strndup(begin, end - begin)) { } + ~StringRange() { free(m_ptr); } + operator const char*() const { return m_ptr; } + const char* get() const { return m_ptr; } + +private: + char* m_ptr; +}; + +class StructBuffer { + WTF_MAKE_NONCOPYABLE(StructBuffer); +public: + StructBuffer(const char* encodedType) + { + NSUInteger size, alignment; + NSGetSizeAndAlignment(encodedType, &size, &alignment); + --alignment; + m_allocation = static_cast<char*>(malloc(size + alignment)); + m_buffer = reinterpret_cast<char*>((reinterpret_cast<intptr_t>(m_allocation) + alignment) & ~alignment); + } + + ~StructBuffer() { free(m_allocation); } + operator void*() const { return m_buffer; } + +private: + void* m_allocation; + void* m_buffer; +}; + +template<typename DelegateType> +typename DelegateType::ResultType parseObjCType(const char*& position) +{ + ASSERT(*position); + + switch (*position++) { + case 'c': + return DelegateType::template typeInteger<char>(); + case 'i': + return DelegateType::template typeInteger<int>(); + case 's': + return DelegateType::template typeInteger<short>(); + case 'l': + return DelegateType::template typeInteger<long>(); + case 'q': + return DelegateType::template typeDouble<unsigned long long>(); + case 'C': + return DelegateType::template typeInteger<unsigned char>(); + case 'I': + return DelegateType::template typeInteger<unsigned>(); + case 'S': + return DelegateType::template typeInteger<unsigned short>(); + case 'L': + return DelegateType::template typeInteger<unsigned long>(); + case 'Q': + return DelegateType::template typeDouble<unsigned long long>(); + case 'f': + return DelegateType::template typeDouble<float>(); + case 'd': + return DelegateType::template typeDouble<double>(); + case 'B': + return DelegateType::typeBool(); + case 'v': + return DelegateType::typeVoid(); + + case '@': { // An object (whether statically typed or typed id) + if (position[0] == '?' && position[1] == '<') { + position += 2; + const char* begin = position; + skipPair<'<','>'>(position); + return DelegateType::typeBlock(begin, position - 1); + } + + if (*position == '"') { + const char* begin = ++position; + position = index(position, '"'); + return DelegateType::typeOfClass(begin, position++); + } + + return DelegateType::typeId(); + } + + case '{': { // {name=type...} A structure + const char* begin = position - 1; + skipPair<'{','}'>(position); + return DelegateType::typeStruct(begin, position); + } + + // NOT supporting C strings, arrays, pointers, unions, bitfields, function pointers. + case '*': // A character string (char *) + case '[': // [array type] An array + case '(': // (name=type...) A union + case 'b': // bnum A bit field of num bits + case '^': // ^type A pointer to type + case '?': // An unknown type (among other things, this code is used for function pointers) + // NOT supporting Objective-C Class, SEL + case '#': // A class object (Class) + case ':': // A method selector (SEL) + default: + return nil; + } +} + +extern "C" { + // Forward declare some Objective-C runtime internal methods that are not API. + const char *_protocol_getMethodTypeEncoding(Protocol *, SEL, BOOL isRequiredMethod, BOOL isInstanceMethod); + id objc_initWeak(id *, id); + void objc_destroyWeak(id *); + bool _Block_has_signature(void *); + const char * _Block_signature(void *); +} diff --git a/Source/JavaScriptCore/API/OpaqueJSString.cpp b/Source/JavaScriptCore/API/OpaqueJSString.cpp index 90bc1c095..a7cef8d9a 100644 --- a/Source/JavaScriptCore/API/OpaqueJSString.cpp +++ b/Source/JavaScriptCore/API/OpaqueJSString.cpp @@ -48,7 +48,7 @@ String OpaqueJSString::string() const return m_string.isolatedCopy(); } -Identifier OpaqueJSString::identifier(JSGlobalData* globalData) const +Identifier OpaqueJSString::identifier(VM* vm) const { if (!this || m_string.isNull()) return Identifier(); @@ -57,7 +57,7 @@ Identifier OpaqueJSString::identifier(JSGlobalData* globalData) const return Identifier(Identifier::EmptyIdentifier); if (m_string.is8Bit()) - return Identifier(globalData, m_string.characters8(), m_string.length()); + return Identifier(vm, m_string.characters8(), m_string.length()); - return Identifier(globalData, m_string.characters16(), m_string.length()); + return Identifier(vm, m_string.characters16(), m_string.length()); } diff --git a/Source/JavaScriptCore/API/OpaqueJSString.h b/Source/JavaScriptCore/API/OpaqueJSString.h index 0464e8813..c374b567a 100644 --- a/Source/JavaScriptCore/API/OpaqueJSString.h +++ b/Source/JavaScriptCore/API/OpaqueJSString.h @@ -31,7 +31,7 @@ namespace JSC { class Identifier; - class JSGlobalData; + class VM; } struct OpaqueJSString : public ThreadSafeRefCounted<OpaqueJSString> { @@ -57,7 +57,10 @@ struct OpaqueJSString : public ThreadSafeRefCounted<OpaqueJSString> { unsigned length() { return !!this ? m_string.length() : 0; } JS_EXPORT_PRIVATE String string() const; - JSC::Identifier identifier(JSC::JSGlobalData*) const; + JSC::Identifier identifier(JSC::VM*) const; +#if PLATFORM(QT) + QString qString() const { return m_string; } +#endif private: friend class WTF::ThreadSafeRefCounted<OpaqueJSString>; @@ -67,12 +70,8 @@ private: } OpaqueJSString(const String& string) + : m_string(string.isolatedCopy()) { - // Make a copy of the source string. - if (string.is8Bit()) - m_string = String(string.characters8(), string.length()); - else - m_string = String(string.characters16(), string.length()); } OpaqueJSString(const LChar* characters, unsigned length) diff --git a/Source/JavaScriptCore/API/tests/JSNode.c b/Source/JavaScriptCore/API/tests/JSNode.c index 052c88a02..d9a40bea6 100644 --- a/Source/JavaScriptCore/API/tests/JSNode.c +++ b/Source/JavaScriptCore/API/tests/JSNode.c @@ -30,7 +30,6 @@ #include "JSValueRef.h" #include "Node.h" #include "NodeList.h" -#include <wtf/UnusedParam.h> #include <wtf/Assertions.h> static JSValueRef JSNode_appendChild(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) diff --git a/Source/JavaScriptCore/API/tests/JSNodeList.c b/Source/JavaScriptCore/API/tests/JSNodeList.c index 0d194845e..61d7041a4 100644 --- a/Source/JavaScriptCore/API/tests/JSNodeList.c +++ b/Source/JavaScriptCore/API/tests/JSNodeList.c @@ -27,7 +27,6 @@ #include "JSNodeList.h" #include "JSObjectRef.h" #include "JSValueRef.h" -#include <wtf/UnusedParam.h> #include <wtf/Assertions.h> static JSValueRef JSNodeList_item(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) diff --git a/Source/JavaScriptCore/API/tests/minidom.c b/Source/JavaScriptCore/API/tests/minidom.c index 43ae2c1a8..8614e51e9 100644 --- a/Source/JavaScriptCore/API/tests/minidom.c +++ b/Source/JavaScriptCore/API/tests/minidom.c @@ -31,7 +31,6 @@ #include <stdio.h> #include <stdlib.h> #include <wtf/Assertions.h> -#include <wtf/UnusedParam.h> static char* createStringWithContentsOfFile(const char* fileName); static JSValueRef print(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); diff --git a/Source/JavaScriptCore/API/tests/testapi.c b/Source/JavaScriptCore/API/tests/testapi.c index c2400f7ec..be18b2bf7 100644 --- a/Source/JavaScriptCore/API/tests/testapi.c +++ b/Source/JavaScriptCore/API/tests/testapi.c @@ -27,10 +27,17 @@ #include "JSBasePrivate.h" #include "JSContextRefPrivate.h" #include "JSObjectRefPrivate.h" +#include "JSScriptRefPrivate.h" +#include "JSStringRefPrivate.h" #include <math.h> #define ASSERT_DISABLED 0 #include <wtf/Assertions.h> -#include <wtf/UnusedParam.h> + +#if PLATFORM(MAC) || PLATFORM(IOS) +#include <mach/mach.h> +#include <mach/mach_time.h> +#include <sys/time.h> +#endif #if OS(WINDOWS) #include <windows.h> @@ -45,10 +52,19 @@ static double nan(const char*) return std::numeric_limits<double>::quiet_NaN(); } +using std::isinf; +using std::isnan; + #endif +#if JSC_OBJC_API_ENABLED +void testObjectiveCAPI(void); +#endif + +extern void JSSynchronousGarbageCollectForDebugging(JSContextRef); + static JSGlobalContextRef context; -static int failed; +int failed; static void assertEqualsAsBoolean(JSValueRef value, bool expectedValue) { if (JSValueToBoolean(context, value) != expectedValue) { @@ -481,6 +497,11 @@ static bool PropertyCatchalls_setProperty(JSContextRef context, JSObjectRef obje return true; } + if (JSStringIsEqualToUTF8CString(propertyName, "make_throw") || JSStringIsEqualToUTF8CString(propertyName, "0")) { + *exception = JSValueMakeNumber(context, 5); + return true; + } + return false; } @@ -1030,6 +1051,68 @@ static void checkConstnessInJSObjectNames() val.name = "something"; } +#if PLATFORM(MAC) || PLATFORM(IOS) +static double currentCPUTime() +{ + mach_msg_type_number_t infoCount = THREAD_BASIC_INFO_COUNT; + thread_basic_info_data_t info; + + /* Get thread information */ + mach_port_t threadPort = mach_thread_self(); + thread_info(threadPort, THREAD_BASIC_INFO, (thread_info_t)(&info), &infoCount); + mach_port_deallocate(mach_task_self(), threadPort); + + double time = info.user_time.seconds + info.user_time.microseconds / 1000000.; + time += info.system_time.seconds + info.system_time.microseconds / 1000000.; + + return time; +} + +static JSValueRef currentCPUTime_callAsFunction(JSContextRef ctx, JSObjectRef functionObject, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + UNUSED_PARAM(functionObject); + UNUSED_PARAM(thisObject); + UNUSED_PARAM(argumentCount); + UNUSED_PARAM(arguments); + UNUSED_PARAM(exception); + + ASSERT(JSContextGetGlobalContext(ctx) == context); + return JSValueMakeNumber(ctx, currentCPUTime()); +} + +bool shouldTerminateCallbackWasCalled = false; +static bool shouldTerminateCallback(JSContextRef ctx, void* context) +{ + UNUSED_PARAM(ctx); + UNUSED_PARAM(context); + shouldTerminateCallbackWasCalled = true; + return true; +} + +bool cancelTerminateCallbackWasCalled = false; +static bool cancelTerminateCallback(JSContextRef ctx, void* context) +{ + UNUSED_PARAM(ctx); + UNUSED_PARAM(context); + cancelTerminateCallbackWasCalled = true; + return false; +} + +int extendTerminateCallbackCalled = 0; +static bool extendTerminateCallback(JSContextRef ctx, void* context) +{ + UNUSED_PARAM(context); + extendTerminateCallbackCalled++; + if (extendTerminateCallbackCalled == 1) { + JSContextGroupRef contextGroup = JSContextGetGroup(ctx); + JSContextGroupSetExecutionTimeLimit(contextGroup, .200f, extendTerminateCallback, 0); + return false; + } + return true; +} +#endif /* PLATFORM(MAC) || PLATFORM(IOS) */ + + int main(int argc, char* argv[]) { #if OS(WINDOWS) @@ -1039,6 +1122,10 @@ int main(int argc, char* argv[]) ::SetErrorMode(0); #endif +#if JSC_OBJC_API_ENABLED + testObjectiveCAPI(); +#endif + const char *scriptPath = "testapi.js"; if (argc > 1) { scriptPath = argv[1]; @@ -1061,6 +1148,8 @@ int main(int argc, char* argv[]) JSClassRef globalObjectClass = JSClassCreate(&globalObjectClassDefinition); context = JSGlobalContextCreateInGroup(NULL, globalObjectClass); + JSContextGroupRef contextGroup = JSContextGetGroup(context); + JSGlobalContextRetain(context); JSGlobalContextRelease(context); ASSERT(JSContextGetGlobalContext(context) == context); @@ -1117,6 +1206,12 @@ int main(int argc, char* argv[]) free(buffer); JSValueRef jsCFEmptyStringWithCharacters = JSValueMakeString(context, jsCFEmptyIStringWithCharacters); + JSChar constantString[] = { 'H', 'e', 'l', 'l', 'o', }; + JSStringRef constantStringRef = JSStringCreateWithCharactersNoCopy(constantString, sizeof(constantString) / sizeof(constantString[0])); + ASSERT(JSStringGetCharactersPtr(constantStringRef) == constantString); + JSStringRelease(constantStringRef); + + ASSERT(JSValueGetType(context, NULL) == kJSTypeNull); ASSERT(JSValueGetType(context, jsUndefined) == kJSTypeUndefined); ASSERT(JSValueGetType(context, jsNull) == kJSTypeNull); ASSERT(JSValueGetType(context, jsTrue) == kJSTypeBoolean); @@ -1131,6 +1226,17 @@ int main(int argc, char* argv[]) ASSERT(JSValueGetType(context, jsCFEmptyString) == kJSTypeString); ASSERT(JSValueGetType(context, jsCFEmptyStringWithCharacters) == kJSTypeString); + ASSERT(!JSValueIsBoolean(context, NULL)); + ASSERT(!JSValueIsObject(context, NULL)); + ASSERT(!JSValueIsString(context, NULL)); + ASSERT(!JSValueIsNumber(context, NULL)); + ASSERT(!JSValueIsUndefined(context, NULL)); + ASSERT(JSValueIsNull(context, NULL)); + ASSERT(!JSObjectCallAsFunction(context, NULL, NULL, 0, NULL, NULL)); + ASSERT(!JSObjectCallAsConstructor(context, NULL, 0, NULL, NULL)); + ASSERT(!JSObjectIsConstructor(context, NULL)); + ASSERT(!JSObjectIsFunction(context, NULL)); + JSStringRef nullString = JSStringCreateWithUTF8CString(0); const JSChar* characters = JSStringGetCharactersPtr(nullString); if (characters) { @@ -1139,6 +1245,14 @@ int main(int argc, char* argv[]) } else printf("PASS: returned null when accessing character pointer of a null String.\n"); + JSStringRef emptyString = JSStringCreateWithCFString(CFSTR("")); + characters = JSStringGetCharactersPtr(emptyString); + if (!characters) { + printf("FAIL: Returned null when accessing character pointer of an empty String.\n"); + failed = 1; + } else + printf("PASS: returned empty when accessing character pointer of an empty String.\n"); + size_t length = JSStringGetLength(nullString); if (length) { printf("FAIL: Didn't return 0 length for null String.\n"); @@ -1147,6 +1261,14 @@ int main(int argc, char* argv[]) printf("PASS: returned 0 length for null String.\n"); JSStringRelease(nullString); + length = JSStringGetLength(emptyString); + if (length) { + printf("FAIL: Didn't return 0 length for empty String.\n"); + failed = 1; + } else + printf("PASS: returned 0 length for empty String.\n"); + JSStringRelease(emptyString); + JSObjectRef propertyCatchalls = JSObjectMake(context, PropertyCatchalls_class(context), NULL); JSStringRef propertyCatchallsString = JSStringCreateWithUTF8CString("PropertyCatchalls"); JSObjectSetProperty(context, globalObject, propertyCatchallsString, propertyCatchalls, kJSPropertyAttributeNone, NULL); @@ -1399,9 +1521,12 @@ int main(int argc, char* argv[]) JSValueUnprotect(context, jsNumberValue); JSStringRef goodSyntax = JSStringCreateWithUTF8CString("x = 1;"); - JSStringRef badSyntax = JSStringCreateWithUTF8CString("x := 1;"); + const char* badSyntaxConstant = "x := 1;"; + JSStringRef badSyntax = JSStringCreateWithUTF8CString(badSyntaxConstant); ASSERT(JSCheckScriptSyntax(context, goodSyntax, NULL, 0, NULL)); ASSERT(!JSCheckScriptSyntax(context, badSyntax, NULL, 0, NULL)); + ASSERT(!JSScriptCreateFromString(contextGroup, 0, 0, badSyntax, 0, 0)); + ASSERT(!JSScriptCreateReferencingImmortalASCIIText(contextGroup, 0, 0, badSyntaxConstant, strlen(badSyntaxConstant), 0, 0)); JSValueRef result; JSValueRef v; @@ -1590,13 +1715,21 @@ int main(int argc, char* argv[]) v = JSObjectCallAsFunction(context, function, o, 0, NULL, NULL); ASSERT(JSValueIsEqual(context, v, o, NULL)); - JSStringRef script = JSStringCreateWithUTF8CString("this;"); + const char* thisScript = "this;"; + JSStringRef script = JSStringCreateWithUTF8CString(thisScript); v = JSEvaluateScript(context, script, NULL, NULL, 1, NULL); ASSERT(JSValueIsEqual(context, v, globalObject, NULL)); v = JSEvaluateScript(context, script, o, NULL, 1, NULL); ASSERT(JSValueIsEqual(context, v, o, NULL)); JSStringRelease(script); + JSScriptRef scriptObject = JSScriptCreateReferencingImmortalASCIIText(contextGroup, 0, 0, thisScript, strlen(thisScript), 0, 0); + v = JSScriptEvaluate(context, scriptObject, NULL, NULL); + ASSERT(JSValueIsEqual(context, v, globalObject, NULL)); + v = JSScriptEvaluate(context, scriptObject, o, NULL); + ASSERT(JSValueIsEqual(context, v, o, NULL)); + JSScriptRelease(scriptObject); + script = JSStringCreateWithUTF8CString("eval(this);"); v = JSEvaluateScript(context, script, NULL, NULL, 1, NULL); ASSERT(JSValueIsEqual(context, v, globalObject, NULL)); @@ -1616,8 +1749,23 @@ int main(int argc, char* argv[]) printf("FAIL: Test script could not be loaded.\n"); failed = 1; } else { - script = JSStringCreateWithUTF8CString(scriptUTF8); - result = JSEvaluateScript(context, script, NULL, NULL, 1, &exception); + JSStringRef url = JSStringCreateWithUTF8CString(scriptPath); + JSStringRef script = JSStringCreateWithUTF8CString(scriptUTF8); + JSStringRef errorMessage = 0; + int errorLine = 0; + JSScriptRef scriptObject = JSScriptCreateFromString(contextGroup, url, 1, script, &errorMessage, &errorLine); + ASSERT((!scriptObject) != (!errorMessage)); + if (!scriptObject) { + printf("FAIL: Test script did not parse\n\t%s:%d\n\t", scriptPath, errorLine); + CFStringRef errorCF = JSStringCopyCFString(kCFAllocatorDefault, errorMessage); + CFShow(errorCF); + CFRelease(errorCF); + JSStringRelease(errorMessage); + failed = 1; + } + + JSStringRelease(script); + result = scriptObject ? JSScriptEvaluate(context, scriptObject, 0, &exception) : 0; if (result && JSValueIsUndefined(context, result)) printf("PASS: Test script executed successfully.\n"); else { @@ -1629,10 +1777,163 @@ int main(int argc, char* argv[]) JSStringRelease(exceptionIString); failed = 1; } - JSStringRelease(script); + JSScriptRelease(scriptObject); free(scriptUTF8); } +#if PLATFORM(MAC) || PLATFORM(IOS) + JSStringRef currentCPUTimeStr = JSStringCreateWithUTF8CString("currentCPUTime"); + JSObjectRef currentCPUTimeFunction = JSObjectMakeFunctionWithCallback(context, currentCPUTimeStr, currentCPUTime_callAsFunction); + JSObjectSetProperty(context, globalObject, currentCPUTimeStr, currentCPUTimeFunction, kJSPropertyAttributeNone, NULL); + JSStringRelease(currentCPUTimeStr); + + /* Test script timeout: */ + JSContextGroupSetExecutionTimeLimit(contextGroup, .10f, shouldTerminateCallback, 0); + { + const char* loopForeverScript = "var startTime = currentCPUTime(); while (true) { if (currentCPUTime() - startTime > .150) break; } "; + JSStringRef script = JSStringCreateWithUTF8CString(loopForeverScript); + double startTime; + double endTime; + exception = NULL; + shouldTerminateCallbackWasCalled = false; + startTime = currentCPUTime(); + v = JSEvaluateScript(context, script, NULL, NULL, 1, &exception); + endTime = currentCPUTime(); + + if (((endTime - startTime) < .150f) && shouldTerminateCallbackWasCalled) + printf("PASS: script timed out as expected.\n"); + else { + if (!((endTime - startTime) < .150f)) + printf("FAIL: script did not timed out as expected.\n"); + if (!shouldTerminateCallbackWasCalled) + printf("FAIL: script timeout callback was not called.\n"); + failed = true; + } + + if (!exception) { + printf("FAIL: TerminatedExecutionException was not thrown.\n"); + failed = true; + } + } + + /* Test the script timeout's TerminatedExecutionException should NOT be catchable: */ + JSContextGroupSetExecutionTimeLimit(contextGroup, 0.10f, shouldTerminateCallback, 0); + { + const char* loopForeverScript = "var startTime = currentCPUTime(); try { while (true) { if (currentCPUTime() - startTime > .150) break; } } catch(e) { }"; + JSStringRef script = JSStringCreateWithUTF8CString(loopForeverScript); + double startTime; + double endTime; + exception = NULL; + shouldTerminateCallbackWasCalled = false; + startTime = currentCPUTime(); + v = JSEvaluateScript(context, script, NULL, NULL, 1, &exception); + endTime = currentCPUTime(); + + if (((endTime - startTime) >= .150f) || !shouldTerminateCallbackWasCalled) { + if (!((endTime - startTime) < .150f)) + printf("FAIL: script did not timed out as expected.\n"); + if (!shouldTerminateCallbackWasCalled) + printf("FAIL: script timeout callback was not called.\n"); + failed = true; + } + + if (exception) + printf("PASS: TerminatedExecutionException was not catchable as expected.\n"); + else { + printf("FAIL: TerminatedExecutionException was caught.\n"); + failed = true; + } + } + + /* Test script timeout with no callback: */ + JSContextGroupSetExecutionTimeLimit(contextGroup, .10f, 0, 0); + { + const char* loopForeverScript = "var startTime = currentCPUTime(); while (true) { if (currentCPUTime() - startTime > .150) break; } "; + JSStringRef script = JSStringCreateWithUTF8CString(loopForeverScript); + double startTime; + double endTime; + exception = NULL; + startTime = currentCPUTime(); + v = JSEvaluateScript(context, script, NULL, NULL, 1, &exception); + endTime = currentCPUTime(); + + if (((endTime - startTime) < .150f) && shouldTerminateCallbackWasCalled) + printf("PASS: script timed out as expected when no callback is specified.\n"); + else { + if (!((endTime - startTime) < .150f)) + printf("FAIL: script did not timed out as expected when no callback is specified.\n"); + failed = true; + } + + if (!exception) { + printf("FAIL: TerminatedExecutionException was not thrown.\n"); + failed = true; + } + } + + /* Test script timeout cancellation: */ + JSContextGroupSetExecutionTimeLimit(contextGroup, 0.10f, cancelTerminateCallback, 0); + { + const char* loopForeverScript = "var startTime = currentCPUTime(); while (true) { if (currentCPUTime() - startTime > .150) break; } "; + JSStringRef script = JSStringCreateWithUTF8CString(loopForeverScript); + double startTime; + double endTime; + exception = NULL; + startTime = currentCPUTime(); + v = JSEvaluateScript(context, script, NULL, NULL, 1, &exception); + endTime = currentCPUTime(); + + if (((endTime - startTime) >= .150f) && cancelTerminateCallbackWasCalled && !exception) + printf("PASS: script timeout was cancelled as expected.\n"); + else { + if (((endTime - startTime) < .150) || exception) + printf("FAIL: script timeout was not cancelled.\n"); + if (!cancelTerminateCallbackWasCalled) + printf("FAIL: script timeout callback was not called.\n"); + failed = true; + } + + if (exception) { + printf("FAIL: Unexpected TerminatedExecutionException thrown.\n"); + failed = true; + } + } + + /* Test script timeout extension: */ + JSContextGroupSetExecutionTimeLimit(contextGroup, 0.100f, extendTerminateCallback, 0); + { + const char* loopForeverScript = "var startTime = currentCPUTime(); while (true) { if (currentCPUTime() - startTime > .500) break; } "; + JSStringRef script = JSStringCreateWithUTF8CString(loopForeverScript); + double startTime; + double endTime; + double deltaTime; + exception = NULL; + startTime = currentCPUTime(); + v = JSEvaluateScript(context, script, NULL, NULL, 1, &exception); + endTime = currentCPUTime(); + deltaTime = endTime - startTime; + + if ((deltaTime >= .300f) && (deltaTime < .500f) && (extendTerminateCallbackCalled == 2) && exception) + printf("PASS: script timeout was extended as expected.\n"); + else { + if (deltaTime < .200f) + printf("FAIL: script timeout was not extended as expected.\n"); + else if (deltaTime >= .500f) + printf("FAIL: script did not timeout.\n"); + + if (extendTerminateCallbackCalled < 1) + printf("FAIL: script timeout callback was not called.\n"); + if (extendTerminateCallbackCalled < 2) + printf("FAIL: script timeout callback was not called after timeout extension.\n"); + + if (!exception) + printf("FAIL: TerminatedExecutionException was not thrown during timeout extension test.\n"); + + failed = true; + } + } +#endif /* PLATFORM(MAC) || PLATFORM(IOS) */ + // Clear out local variables pointing at JSObjectRefs to allow their values to be collected function = NULL; v = NULL; diff --git a/Source/JavaScriptCore/API/tests/testapi.js b/Source/JavaScriptCore/API/tests/testapi.js index 28fa54433..47c20a830 100644 --- a/Source/JavaScriptCore/API/tests/testapi.js +++ b/Source/JavaScriptCore/API/tests/testapi.js @@ -262,6 +262,10 @@ shouldBe("PropertyCatchalls.x", 4); for (var i = 0; i < 6; ++i) var x = PropertyCatchalls.x; shouldBe("x", null); +var make_throw = 'make_throw'; +shouldThrow("PropertyCatchalls[make_throw]=1"); +make_throw = 0; +shouldThrow("PropertyCatchalls[make_throw]=1"); for (var i = 0; i < 10; ++i) { for (var p in PropertyCatchalls) { diff --git a/Source/JavaScriptCore/API/tests/testapi.mm b/Source/JavaScriptCore/API/tests/testapi.mm new file mode 100644 index 000000000..defb46e70 --- /dev/null +++ b/Source/JavaScriptCore/API/tests/testapi.mm @@ -0,0 +1,841 @@ +/* + * 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. + */ + +#import <JavaScriptCore/JavaScriptCore.h> + +extern "C" void JSSynchronousGarbageCollectForDebugging(JSContextRef); + +extern "C" bool _Block_has_signature(id); +extern "C" const char * _Block_signature(id); + +extern int failed; +extern "C" void testObjectiveCAPI(void); + +#if JSC_OBJC_API_ENABLED + +@protocol ParentObject <JSExport> +@end + +@interface ParentObject : NSObject<ParentObject> ++ (NSString *)parentTest; +@end + +@implementation ParentObject ++ (NSString *)parentTest +{ + return [self description]; +} +@end + +@protocol TestObject <JSExport> +@property int variable; +@property (readonly) int six; +@property CGPoint point; ++ (NSString *)classTest; ++ (NSString *)parentTest; +- (NSString *)getString; +JSExportAs(testArgumentTypes, +- (NSString *)testArgumentTypesWithInt:(int)i double:(double)d boolean:(BOOL)b string:(NSString *)s number:(NSNumber *)n array:(NSArray *)a dictionary:(NSDictionary *)o +); +- (void)callback:(JSValue *)function; +- (void)bogusCallback:(void(^)(int))function; +@end + +@interface TestObject : ParentObject <TestObject> +@property int six; ++ (id)testObject; +@end + +@implementation TestObject +@synthesize variable; +@synthesize six; +@synthesize point; ++ (id)testObject +{ + return [[TestObject alloc] init]; +} ++ (NSString *)classTest +{ + return @"classTest - okay"; +} +- (NSString *)getString +{ + return @"42"; +} +- (NSString *)testArgumentTypesWithInt:(int)i double:(double)d boolean:(BOOL)b string:(NSString *)s number:(NSNumber *)n array:(NSArray *)a dictionary:(NSDictionary *)o +{ + return [NSString stringWithFormat:@"%d,%g,%d,%@,%d,%@,%@", i, d, b==YES?true:false,s,[n intValue],a[1],o[@"x"]]; +} +- (void)callback:(JSValue *)function +{ + [function callWithArguments:[NSArray arrayWithObject:[NSNumber numberWithInt:42]]]; +} +- (void)bogusCallback:(void(^)(int))function +{ + function(42); +} +@end + +bool testXYZTested = false; + +@protocol TextXYZ <JSExport> +@property int x; +@property (readonly) int y; +@property (assign) JSValue *onclick; +@property (assign) JSValue *weakOnclick; +- (void)test:(NSString *)message; +@end + +@interface TextXYZ : NSObject <TextXYZ> +@property int x; +@property int y; +@property int z; +- (void)click; +@end + +@implementation TextXYZ { + JSManagedValue *m_weakOnclickHandler; + JSManagedValue *m_onclickHandler; +} +@synthesize x; +@synthesize y; +@synthesize z; +- (void)test:(NSString *)message +{ + testXYZTested = [message isEqual:@"test"] && x == 13 & y == 4 && z == 5; +} +- (void)setWeakOnclick:(JSValue *)value +{ + m_weakOnclickHandler = [JSManagedValue managedValueWithValue:value]; +} + +- (void)setOnclick:(JSValue *)value +{ + m_onclickHandler = [JSManagedValue managedValueWithValue:value]; + [value.context.virtualMachine addManagedReference:m_onclickHandler withOwner:self]; +} +- (JSValue *)weakOnclick +{ + return [m_weakOnclickHandler value]; +} +- (JSValue *)onclick +{ + return [m_onclickHandler value]; +} +- (void)click +{ + if (!m_onclickHandler) + return; + + JSValue *function = [m_onclickHandler value]; + [function callWithArguments:[NSArray array]]; +} +- (void)dealloc +{ + [[m_onclickHandler value].context.virtualMachine removeManagedReference:m_onclickHandler withOwner:self]; +} +@end + +@class TinyDOMNode; + +@protocol TinyDOMNode <JSExport> +- (void)appendChild:(TinyDOMNode *)child; +- (NSUInteger)numberOfChildren; +- (TinyDOMNode *)childAtIndex:(NSUInteger)index; +- (void)removeChildAtIndex:(NSUInteger)index; +@end + +@interface TinyDOMNode : NSObject<TinyDOMNode> ++ (JSVirtualMachine *)sharedVirtualMachine; ++ (void)clearSharedVirtualMachine; +@end + +@implementation TinyDOMNode { + NSMutableArray *m_children; +} + +static JSVirtualMachine *sharedInstance = nil; + ++ (JSVirtualMachine *)sharedVirtualMachine +{ + if (!sharedInstance) + sharedInstance = [[JSVirtualMachine alloc] init]; + return sharedInstance; +} + ++ (void)clearSharedVirtualMachine +{ + sharedInstance = nil; +} + +- (id)init +{ + self = [super init]; + if (!self) + return nil; + + m_children = [[NSMutableArray alloc] initWithCapacity:0]; + + return self; +} + +- (void)dealloc +{ + NSEnumerator *enumerator = [m_children objectEnumerator]; + id nextChild; + while ((nextChild = [enumerator nextObject])) + [[TinyDOMNode sharedVirtualMachine] removeManagedReference:nextChild withOwner:self]; + +#if !__has_feature(objc_arc) + [super dealloc]; +#endif +} + +- (void)appendChild:(TinyDOMNode *)child +{ + [[TinyDOMNode sharedVirtualMachine] addManagedReference:child withOwner:self]; + [m_children addObject:child]; +} + +- (NSUInteger)numberOfChildren +{ + return [m_children count]; +} + +- (TinyDOMNode *)childAtIndex:(NSUInteger)index +{ + if (index >= [m_children count]) + return nil; + return [m_children objectAtIndex:index]; +} + +- (void)removeChildAtIndex:(NSUInteger)index +{ + if (index >= [m_children count]) + return; + [[TinyDOMNode sharedVirtualMachine] removeManagedReference:[m_children objectAtIndex:index] withOwner:self]; + [m_children removeObjectAtIndex:index]; +} + +@end + +static void checkResult(NSString *description, bool passed) +{ + NSLog(@"TEST: \"%@\": %@", description, passed ? @"PASSED" : @"FAILED"); + if (!passed) + failed = 1; +} + +static bool blockSignatureContainsClass() +{ + static bool containsClass = ^{ + id block = ^(NSString *string){ return string; }; + return _Block_has_signature(block) && strstr(_Block_signature(block), "NSString"); + }(); + return containsClass; +} + +void testObjectiveCAPI() +{ + NSLog(@"Testing Objective-C API"); + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + JSValue *result = [context evaluateScript:@"2 + 2"]; + checkResult(@"2 + 2", [result isNumber] && [result toInt32] == 4); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + NSString *result = [NSString stringWithFormat:@"Two plus two is %@", [context evaluateScript:@"2 + 2"]]; + checkResult(@"stringWithFormat", [result isEqual:@"Two plus two is 4"]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + context[@"message"] = @"Hello"; + JSValue *result = [context evaluateScript:@"message + ', World!'"]; + checkResult(@"Hello, World!", [result isString] && [result isEqualToObject:@"Hello, World!"]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + JSValue *result = [context evaluateScript:@"({ x:42 })"]; + checkResult(@"({ x:42 })", [result isObject] && [result[@"x"] isEqualToObject:@42]); + id obj = [result toObject]; + checkResult(@"Check dictionary literal", [obj isKindOfClass:[NSDictionary class]]); + id num = (NSDictionary *)obj[@"x"]; + checkResult(@"Check numeric literal", [num isKindOfClass:[NSNumber class]]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + __block int result; + context[@"blockCallback"] = ^(int value){ + result = value; + }; + [context evaluateScript:@"blockCallback(42)"]; + checkResult(@"blockCallback", result == 42); + } + + if (blockSignatureContainsClass()) { + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + __block bool result = false; + context[@"blockCallback"] = ^(NSString *value){ + result = [@"42" isEqualToString:value] == YES; + }; + [context evaluateScript:@"blockCallback(42)"]; + checkResult(@"blockCallback(NSString *)", result); + } + } else + NSLog(@"Skipping 'blockCallback(NSString *)' test case"); + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + checkResult(@"!context.exception", !context.exception); + [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"]; + checkResult(@"context.exception", context.exception); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + __block bool caught = false; + context.exceptionHandler = ^(JSContext *context, JSValue *exception) { + (void)context; + (void)exception; + caught = true; + }; + [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"]; + checkResult(@"JSContext.exceptionHandler", caught); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + context[@"callback"] = ^{ + JSContext *context = [JSContext currentContext]; + context.exception = [JSValue valueWithNewErrorFromMessage:@"Something went wrong." inContext:context]; + }; + JSValue *result = [context evaluateScript:@"var result; try { callback(); } catch (e) { result = 'Caught exception'; }"]; + checkResult(@"Explicit throw in callback - was caught by JavaScript", [result isEqualToObject:@"Caught exception"]); + checkResult(@"Explicit throw in callback - not thrown to Objective-C", !context.exception); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + context[@"callback"] = ^{ + JSContext *context = [JSContext currentContext]; + [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"]; + }; + JSValue *result = [context evaluateScript:@"var result; try { callback(); } catch (e) { result = 'Caught exception'; }"]; + checkResult(@"Implicit throw in callback - was caught by JavaScript", [result isEqualToObject:@"Caught exception"]); + checkResult(@"Implicit throw in callback - not thrown to Objective-C", !context.exception); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + [context evaluateScript: + @"function sum(array) { \ + var result = 0; \ + for (var i in array) \ + result += array[i]; \ + return result; \ + }"]; + JSValue *array = [JSValue valueWithObject:@[@13, @2, @7] inContext:context]; + JSValue *sumFunction = context[@"sum"]; + JSValue *result = [sumFunction callWithArguments:@[ array ]]; + checkResult(@"sum([13, 2, 7])", [result toInt32] == 22); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + JSValue *mulAddFunction = [context evaluateScript: + @"(function(array, object) { \ + var result = []; \ + for (var i in array) \ + result.push(array[i] * object.x + object.y); \ + return result; \ + })"]; + JSValue *result = [mulAddFunction callWithArguments:@[ @[ @2, @4, @8 ], @{ @"x":@0.5, @"y":@42 } ]]; + checkResult(@"mulAddFunction", [result isObject] && [[result toString] isEqual:@"43,44,46"]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + JSValue *array = [JSValue valueWithNewArrayInContext:context]; + checkResult(@"arrayLengthEmpty", [[array[@"length"] toNumber] unsignedIntegerValue] == 0); + JSValue *value1 = [JSValue valueWithInt32:42 inContext:context]; + JSValue *value2 = [JSValue valueWithInt32:24 inContext:context]; + NSUInteger lowIndex = 5; + NSUInteger maxLength = UINT_MAX; + + [array setValue:value1 atIndex:lowIndex]; + checkResult(@"array.length after put to low index", [[array[@"length"] toNumber] unsignedIntegerValue] == (lowIndex + 1)); + + [array setValue:value1 atIndex:(maxLength - 1)]; + checkResult(@"array.length after put to maxLength - 1", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength); + + [array setValue:value2 atIndex:maxLength]; + checkResult(@"array.length after put to maxLength", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength); + + [array setValue:value2 atIndex:(maxLength + 1)]; + checkResult(@"array.length after put to maxLength + 1", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength); + + checkResult(@"valueAtIndex:0 is undefined", [[array valueAtIndex:0] isUndefined]); + checkResult(@"valueAtIndex:lowIndex", [[array valueAtIndex:lowIndex] toInt32] == 42); + checkResult(@"valueAtIndex:maxLength - 1", [[array valueAtIndex:(maxLength - 1)] toInt32] == 42); + checkResult(@"valueAtIndex:maxLength", [[array valueAtIndex:maxLength] toInt32] == 24); + checkResult(@"valueAtIndex:maxLength + 1", [[array valueAtIndex:(maxLength + 1)] toInt32] == 24); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + JSValue *object = [JSValue valueWithNewObjectInContext:context]; + + object[@"point"] = @{ @"x":@1, @"y":@2 }; + object[@"point"][@"x"] = @3; + CGPoint point = [object[@"point"] toPoint]; + checkResult(@"toPoint", point.x == 3 && point.y == 2); + + object[@{ @"toString":^{ return @"foo"; } }] = @"bar"; + checkResult(@"toString in object literal used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]); + + object[[@"foobar" substringToIndex:3]] = @"bar"; + checkResult(@"substring used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + TextXYZ *testXYZ = [[TextXYZ alloc] init]; + context[@"testXYZ"] = testXYZ; + testXYZ.x = 3; + testXYZ.y = 4; + testXYZ.z = 5; + [context evaluateScript:@"testXYZ.x = 13; testXYZ.y = 14;"]; + [context evaluateScript:@"testXYZ.test('test')"]; + checkResult(@"TextXYZ - testXYZTested", testXYZTested); + JSValue *result = [context evaluateScript:@"testXYZ.x + ',' + testXYZ.y + ',' + testXYZ.z"]; + checkResult(@"TextXYZ - result", [result isEqualToObject:@"13,4,undefined"]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + [context[@"Object"][@"prototype"] defineProperty:@"getterProperty" descriptor:@{ + JSPropertyDescriptorGetKey:^{ + return [JSContext currentThis][@"x"]; + } + }]; + JSValue *object = [JSValue valueWithObject:@{ @"x":@101 } inContext:context]; + int result = [object [@"getterProperty"] toInt32]; + checkResult(@"getterProperty", result == 101); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + context[@"concatenate"] = ^{ + NSArray *arguments = [JSContext currentArguments]; + if (![arguments count]) + return @""; + NSString *message = [arguments[0] description]; + for (NSUInteger index = 1; index < [arguments count]; ++index) + message = [NSString stringWithFormat:@"%@ %@", message, arguments[index]]; + return message; + }; + JSValue *result = [context evaluateScript:@"concatenate('Hello,', 'World!')"]; + checkResult(@"concatenate", [result isEqualToObject:@"Hello, World!"]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + context[@"foo"] = @YES; + checkResult(@"@YES is boolean", [context[@"foo"] isBoolean]); + JSValue *result = [context evaluateScript:@"typeof foo"]; + checkResult(@"@YES is boolean", [result isEqualToObject:@"boolean"]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + TestObject* testObject = [TestObject testObject]; + context[@"testObject"] = testObject; + JSValue *result = [context evaluateScript:@"String(testObject)"]; + checkResult(@"String(testObject)", [result isEqualToObject:@"[object TestObject]"]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + TestObject* testObject = [TestObject testObject]; + context[@"testObject"] = testObject; + JSValue *result = [context evaluateScript:@"String(testObject.__proto__)"]; + checkResult(@"String(testObject.__proto__)", [result isEqualToObject:@"[object TestObjectPrototype]"]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + context[@"TestObject"] = [TestObject class]; + JSValue *result = [context evaluateScript:@"String(TestObject)"]; + checkResult(@"String(TestObject)", [result isEqualToObject:@"[object TestObjectConstructor]"]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + JSValue* value = [JSValue valueWithObject:[TestObject class] inContext:context]; + checkResult(@"[value toObject] == [TestObject class]", [value toObject] == [TestObject class]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + context[@"TestObject"] = [TestObject class]; + JSValue *result = [context evaluateScript:@"TestObject.parentTest()"]; + checkResult(@"TestObject.parentTest()", [result isEqualToObject:@"TestObject"]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + TestObject* testObject = [TestObject testObject]; + context[@"testObjectA"] = testObject; + context[@"testObjectB"] = testObject; + JSValue *result = [context evaluateScript:@"testObjectA == testObjectB"]; + checkResult(@"testObjectA == testObjectB", [result isBoolean] && [result toBool]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + TestObject* testObject = [TestObject testObject]; + context[@"testObject"] = testObject; + testObject.point = (CGPoint){3,4}; + JSValue *result = [context evaluateScript:@"var result = JSON.stringify(testObject.point); testObject.point = {x:12,y:14}; result"]; + checkResult(@"testObject.point - result", [result isEqualToObject:@"{\"x\":3,\"y\":4}"]); + checkResult(@"testObject.point - {x:12,y:14}", testObject.point.x == 12 && testObject.point.y == 14); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + TestObject* testObject = [TestObject testObject]; + testObject.six = 6; + context[@"testObject"] = testObject; + context[@"mul"] = ^(int x, int y){ return x * y; }; + JSValue *result = [context evaluateScript:@"mul(testObject.six, 7)"]; + checkResult(@"mul(testObject.six, 7)", [result isNumber] && [result toInt32] == 42); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + TestObject* testObject = [TestObject testObject]; + context[@"testObject"] = testObject; + context[@"testObject"][@"variable"] = @4; + [context evaluateScript:@"++testObject.variable"]; + checkResult(@"++testObject.variable", testObject.variable == 5); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + context[@"point"] = @{ @"x":@6, @"y":@7 }; + JSValue *result = [context evaluateScript:@"point.x + ',' + point.y"]; + checkResult(@"point.x + ',' + point.y", [result isEqualToObject:@"6,7"]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + context[@"point"] = @{ @"x":@6, @"y":@7 }; + JSValue *result = [context evaluateScript:@"point.x + ',' + point.y"]; + checkResult(@"point.x + ',' + point.y", [result isEqualToObject:@"6,7"]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + TestObject* testObject = [TestObject testObject]; + context[@"testObject"] = testObject; + JSValue *result = [context evaluateScript:@"testObject.getString()"]; + checkResult(@"testObject.getString()", [result isString] && [result toInt32] == 42); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + TestObject* testObject = [TestObject testObject]; + context[@"testObject"] = testObject; + JSValue *result = [context evaluateScript:@"testObject.testArgumentTypes(101,0.5,true,'foo',666,[false,'bar',false],{x:'baz'})"]; + checkResult(@"testObject.testArgumentTypes", [result isEqualToObject:@"101,0.5,1,foo,666,bar,baz"]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + TestObject* testObject = [TestObject testObject]; + context[@"testObject"] = testObject; + JSValue *result = [context evaluateScript:@"testObject.getString.call(testObject)"]; + checkResult(@"testObject.getString.call(testObject)", [result isString] && [result toInt32] == 42); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + TestObject* testObject = [TestObject testObject]; + context[@"testObject"] = testObject; + checkResult(@"testObject.getString.call({}) pre", !context.exception); + [context evaluateScript:@"testObject.getString.call({})"]; + checkResult(@"testObject.getString.call({}) post", context.exception); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + TestObject* testObject = [TestObject testObject]; + context[@"testObject"] = testObject; + JSValue *result = [context evaluateScript:@"var result = 0; testObject.callback(function(x){ result = x; }); result"]; + checkResult(@"testObject.callback", [result isNumber] && [result toInt32] == 42); + result = [context evaluateScript:@"testObject.bogusCallback"]; + checkResult(@"testObject.bogusCallback == undefined", [result isUndefined]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + TestObject *testObject = [TestObject testObject]; + context[@"testObject"] = testObject; + JSValue *result = [context evaluateScript:@"Function.prototype.toString.call(testObject.callback)"]; + checkResult(@"Function.prototype.toString", !context.exception && ![result isUndefined]); + } + + @autoreleasepool { + JSContext *context1 = [[JSContext alloc] init]; + JSContext *context2 = [[JSContext alloc] initWithVirtualMachine:context1.virtualMachine]; + JSValue *value = [JSValue valueWithDouble:42 inContext:context2]; + context1[@"passValueBetweenContexts"] = value; + JSValue *result = [context1 evaluateScript:@"passValueBetweenContexts"]; + checkResult(@"[value isEqualToObject:result]", [value isEqualToObject:result]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + context[@"handleTheDictionary"] = ^(NSDictionary *dict) { + NSDictionary *expectedDict = @{ + @"foo" : [NSNumber numberWithInt:1], + @"bar" : @{ + @"baz": [NSNumber numberWithInt:2] + } + }; + checkResult(@"recursively convert nested dictionaries", [dict isEqualToDictionary:expectedDict]); + }; + [context evaluateScript:@"var myDict = { \ + 'foo': 1, \ + 'bar': {'baz': 2} \ + }; \ + handleTheDictionary(myDict);"]; + + context[@"handleTheArray"] = ^(NSArray *array) { + NSArray *expectedArray = @[@"foo", @"bar", @[@"baz"]]; + checkResult(@"recursively convert nested arrays", [array isEqualToArray:expectedArray]); + }; + [context evaluateScript:@"var myArray = ['foo', 'bar', ['baz']]; handleTheArray(myArray);"]; + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + TestObject *testObject = [TestObject testObject]; + @autoreleasepool { + context[@"testObject"] = testObject; + [context evaluateScript:@"var constructor = Object.getPrototypeOf(testObject).constructor; constructor.prototype = undefined;"]; + [context evaluateScript:@"testObject = undefined"]; + } + + JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]); + + @autoreleasepool { + context[@"testObject"] = testObject; + } + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + TextXYZ *testXYZ = [[TextXYZ alloc] init]; + + @autoreleasepool { + context[@"testXYZ"] = testXYZ; + + [context evaluateScript:@" \ + didClick = false; \ + testXYZ.onclick = function() { \ + didClick = true; \ + }; \ + \ + testXYZ.weakOnclick = function() { \ + return 'foo'; \ + }; \ + "]; + } + + @autoreleasepool { + [testXYZ click]; + JSValue *result = [context evaluateScript:@"didClick"]; + checkResult(@"Event handler onclick", [result toBool]); + } + + JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]); + + @autoreleasepool { + JSValue *result = [context evaluateScript:@"testXYZ.onclick"]; + checkResult(@"onclick still around after GC", !([result isNull] || [result isUndefined])); + } + + + @autoreleasepool { + JSValue *result = [context evaluateScript:@"testXYZ.weakOnclick"]; + checkResult(@"weakOnclick not around after GC", [result isNull] || [result isUndefined]); + } + + @autoreleasepool { + [context evaluateScript:@" \ + didClick = false; \ + testXYZ = null; \ + "]; + } + + JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]); + + @autoreleasepool { + [testXYZ click]; + JSValue *result = [context evaluateScript:@"didClick"]; + checkResult(@"Event handler onclick doesn't fire", ![result toBool]); + } + } + + @autoreleasepool { + JSVirtualMachine *vm = [[JSVirtualMachine alloc] init]; + TestObject *testObject = [TestObject testObject]; + JSManagedValue *weakValue; + @autoreleasepool { + JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm]; + context[@"testObject"] = testObject; + weakValue = [[JSManagedValue alloc] initWithValue:context[@"testObject"]]; + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm]; + context[@"testObject"] = testObject; + JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]); + checkResult(@"weak value == nil", ![weakValue value]); + checkResult(@"root is still alive", ![context[@"testObject"] isUndefined]); + } + } + + @autoreleasepool { + JSVirtualMachine *vm = [TinyDOMNode sharedVirtualMachine]; + JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm]; + TinyDOMNode *root = [[TinyDOMNode alloc] init]; + TinyDOMNode *lastNode = root; + for (NSUInteger i = 0; i < 3; i++) { + TinyDOMNode *newNode = [[TinyDOMNode alloc] init]; + [lastNode appendChild:newNode]; + lastNode = newNode; + } + + @autoreleasepool { + context[@"root"] = root; + context[@"getLastNodeInChain"] = ^(TinyDOMNode *head){ + TinyDOMNode *lastNode = nil; + while (head) { + lastNode = head; + head = [lastNode childAtIndex:0]; + } + return lastNode; + }; + [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty = 42;"]; + } + + JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]); + + JSValue *myCustomProperty = [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty"]; + checkResult(@"My custom property == 42", [myCustomProperty isNumber] && [myCustomProperty toInt32] == 42); + + [TinyDOMNode clearSharedVirtualMachine]; + } + + @autoreleasepool { + JSVirtualMachine *vm = [TinyDOMNode sharedVirtualMachine]; + JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm]; + TinyDOMNode *root = [[TinyDOMNode alloc] init]; + TinyDOMNode *lastNode = root; + for (NSUInteger i = 0; i < 3; i++) { + TinyDOMNode *newNode = [[TinyDOMNode alloc] init]; + [lastNode appendChild:newNode]; + lastNode = newNode; + } + + @autoreleasepool { + context[@"root"] = root; + context[@"getLastNodeInChain"] = ^(TinyDOMNode *head){ + TinyDOMNode *lastNode = nil; + while (head) { + lastNode = head; + head = [lastNode childAtIndex:0]; + } + return lastNode; + }; + [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty = 42;"]; + + [root appendChild:[root childAtIndex:0]]; + [root removeChildAtIndex:0]; + } + + JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]); + + JSValue *myCustomProperty = [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty"]; + checkResult(@"duplicate calls to addManagedReference don't cause things to die", [myCustomProperty isNumber] && [myCustomProperty toInt32] == 42); + + [TinyDOMNode clearSharedVirtualMachine]; + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + JSValue *o = [JSValue valueWithNewObjectInContext:context]; + o[@"foo"] = @"foo"; + JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]); + + checkResult(@"JSValue correctly protected its internal value", [[o[@"foo"] toString] isEqualToString:@"foo"]); + } + + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + TestObject *testObject = [TestObject testObject]; + context[@"testObject"] = testObject; + [context evaluateScript:@"testObject.__lookupGetter__('variable').call({})"]; + checkResult(@"Make sure we throw an exception when calling getter on incorrect |this|", context.exception); + } + + @autoreleasepool { + TestObject *testObject = [TestObject testObject]; + JSManagedValue *managedTestObject; + @autoreleasepool { + JSContext *context = [[JSContext alloc] init]; + context[@"testObject"] = testObject; + managedTestObject = [JSManagedValue managedValueWithValue:context[@"testObject"]]; + [context.virtualMachine addManagedReference:managedTestObject withOwner:testObject]; + } + } +} + +#else + +void testObjectiveCAPI() +{ +} + +#endif |