diff --git a/change/react-native-windows-2019-10-16-17-50-01-CharkaObjectRef.json b/change/react-native-windows-2019-10-16-17-50-01-CharkaObjectRef.json new file mode 100644 index 00000000000..1dd624cd858 --- /dev/null +++ b/change/react-native-windows-2019-10-16-17-50-01-CharkaObjectRef.json @@ -0,0 +1,9 @@ +{ + "type": "none", + "comment": "Introduce ChakraPointerValue and reimplement many Runtime functions with it.", + "packageName": "react-native-windows", + "email": "yicyao@microsoft.com", + "commit": "af2be74461b33fa6fdcafbb53d4f4388b16a98d6", + "date": "2019-10-17T00:50:01.129Z", + "file": "D:\\Git\\hansenyy-react-native-windows-3\\change\\react-native-windows-2019-10-16-17-50-01-CharkaObjectRef.json" +} \ No newline at end of file diff --git a/vnext/JSI.Desktop.UnitTests/JsiRuntimeUnitTests.cpp b/vnext/JSI.Desktop.UnitTests/JsiRuntimeUnitTests.cpp index 2d3344d5040..6cc5826fdf7 100644 --- a/vnext/JSI.Desktop.UnitTests/JsiRuntimeUnitTests.cpp +++ b/vnext/JSI.Desktop.UnitTests/JsiRuntimeUnitTests.cpp @@ -28,9 +28,7 @@ TEST_P(JsiRuntimeUnitTests, RuntimeTest) { EXPECT_EQ(rt.global().getProperty(rt, "x").getNumber(), 1); } -// TODO (yicyao) #2703: Currently, comparison of property IDs is broken for -// ChakraRuntime. Enable this test once we fix it. -TEST_P(JsiRuntimeUnitTests, DISABLED_PropNameIDTest) { +TEST_P(JsiRuntimeUnitTests, PropNameIDTest) { // This is a little weird to test, because it doesn't really exist // in JS yet. All I can do is create them, compare them, and // receive one as an argument to a HostObject. @@ -926,9 +924,6 @@ TEST_P(JsiRuntimeUnitTests, DISABLED_ExceptionStackTraceTest) { EXPECT_NE(stack.find("world"), std::string::npos); } -// TODO (T28293178) Remove this once exceptions are supported in all builds. -#ifndef JSI_NO_EXCEPTION_TESTS - namespace { unsigned countOccurences(const std::string &of, const std::string &in) { @@ -944,7 +939,7 @@ unsigned countOccurences(const std::string &of, const std::string &in) { } // namespace TEST_P(JsiRuntimeUnitTests, JSErrorsArePropagatedNicely) { - unsigned callsBeoreError = 5; + unsigned callsBeforeError = 5; Function sometimesThrows = function( "function sometimesThrows(shouldThrow, callback) {" @@ -958,25 +953,21 @@ TEST_P(JsiRuntimeUnitTests, JSErrorsArePropagatedNicely) { rt, PropNameID::forAscii(rt, "callback"), 0, - [&sometimesThrows, &callsBeoreError]( + [&sometimesThrows, &callsBeforeError]( Runtime &rt, const Value &thisVal, const Value *args, size_t count) { - return sometimesThrows.call(rt, --callsBeoreError == 0, args[0]); + return sometimesThrows.call(rt, --callsBeforeError == 0, args[0]); }); try { sometimesThrows.call(rt, false, callback); } catch (JSError &error) { - // TODO (yicyao): Need to fix getMessage(). - // EXPECT_EQ(error.getMessage(), "Omg, what a nasty exception"); - - // TODO (yicyao): Need to fix getStack(). - // EXPECT_EQ(countOccurences("sometimesThrows", error.getStack()), 6); + EXPECT_EQ(error.getMessage(), "Omg, what a nasty exception"); + EXPECT_EQ(countOccurences("sometimesThrows", error.getStack()), 6); // system JSC JSI does not implement host function names // EXPECT_EQ(countOccurences("callback", error.getStack(rt)), 5); } } -#endif TEST_P(JsiRuntimeUnitTests, JSErrorsCanBeConstructedWithStack) { auto err = JSError(rt, "message", "stack"); diff --git a/vnext/JSI/Desktop/ChakraJsiRuntime_core.cpp b/vnext/JSI/Desktop/ChakraJsiRuntime_core.cpp index 2fde9c6f258..1014ee0c5fa 100644 --- a/vnext/JSI/Desktop/ChakraJsiRuntime_core.cpp +++ b/vnext/JSI/Desktop/ChakraJsiRuntime_core.cpp @@ -3,9 +3,10 @@ #include "ChakraRuntime.h" #include "ChakraRuntimeFactory.h" -#include "Unicode.h" #include +#include "ByteArrayBuffer.h" +#include "Unicode.h" // This file contains non-edge-mode (or win32) implementations. #if !defined(USE_EDGEMODE_JSRT) @@ -35,35 +36,8 @@ struct FileVersionInfoResource { } // namespace -JsWeakRef ChakraRuntime::newWeakObjectRef(const facebook::jsi::Object &obj) { - JsWeakRef weakRef; - JsCreateWeakReference(objectRef(obj), &weakRef); - return weakRef; -} - -JsValueRef ChakraRuntime::strongObjectRef( - const facebook::jsi::WeakObject &obj) { - JsValueRef strongRef; - JsGetWeakReferenceValue(objectRef(obj), &strongRef); - return strongRef; -} - -// Note :: ChakraCore header provides an API which takes 8-bit string .. which -// is not available in edge mode. -JsValueRef ChakraRuntime::createJSString(const char *data, size_t length) { - JsValueRef value; - JsCreateString(reinterpret_cast(data), length, &value); - return value; -} - -// Note :: ChakraCore header provides an API which takes 8-bit string .. which -// is not available in edge mode. -JsValueRef ChakraRuntime::createJSPropertyId(const char *data, size_t length) { - JsValueRef propIdRef; - if (JsNoError != JsCreatePropertyId(data, length, &propIdRef)) - std::terminate(); - return propIdRef; -} +// TODO (yicyao): We temporarily removed weak reference semantics from +// ChakraCore based jsi::Runtime. // ES6 Promise callback void CALLBACK ChakraRuntime::PromiseContinuationCallback( @@ -88,7 +62,8 @@ void ChakraRuntime::PromiseContinuation(JsValueRef funcRef) noexcept { runtimeArgs().jsQueue->runOnQueue([this, funcRef]() { JsValueRef undefinedValue; JsGetUndefinedValue(&undefinedValue); - checkException(JsCallFunction(funcRef, &undefinedValue, 1, nullptr)); + VerifyJsErrorElseThrow( + JsCallFunction(funcRef, &undefinedValue, 1, nullptr)); JsRelease(funcRef, nullptr); }); } @@ -111,7 +86,7 @@ void ChakraRuntime::PromiseRejectionTracker( JsValueRef stackStrValue; error = JsConvertValueToString(stack, &stackStrValue); if (error == JsNoError) { - errorStream << JSStringToSTLString(stackStrValue); + errorStream << ToStdString(ChakraObjectRef(stackStrValue)); } } } @@ -121,7 +96,7 @@ void ChakraRuntime::PromiseRejectionTracker( JsValueRef strValue; error = JsConvertValueToString(reason, &strValue); if (error == JsNoError) { - errorStream << JSStringToSTLString(strValue); + errorStream << ToStdString(ChakraObjectRef(strValue)); } } @@ -325,16 +300,14 @@ facebook::jsi::Value ChakraRuntime::evaluateJavaScriptSimple( &sourceURLRef); JsValueRef result; - checkException( - JsRun( - sourceRef, - 0, - sourceURLRef, - JsParseScriptAttributes::JsParseScriptAttributeNone, - &result), - sourceURL.c_str()); - - return createValue(result); + VerifyJsErrorElseThrow(JsRun( + sourceRef, + 0, + sourceURLRef, + JsParseScriptAttributes::JsParseScriptAttributeNone, + &result)); + + return ToJsiValue(ChakraObjectRef(result)); } // TODO :: Return result @@ -351,8 +324,8 @@ bool ChakraRuntime::evaluateSerializedScript( &bytecodeArrayBuffer) == JsNoError) { JsValueRef sourceURLRef = nullptr; if (!sourceURL.empty()) { - sourceURLRef = createJSString( - reinterpret_cast(sourceURL.c_str()), sourceURL.size()); + sourceURLRef = ToJsString(std::string_view{ + reinterpret_cast(sourceURL.c_str()), sourceURL.size()}); } JsValueRef value = nullptr; @@ -383,7 +356,7 @@ bool ChakraRuntime::evaluateSerializedScript( } else if (result == JsErrorBadSerializedScript) { return false; } else { - checkException(result); + VerifyChakraErrorElseThrow(result); } } diff --git a/vnext/JSI/Shared/ByteArrayBuffer.h b/vnext/JSI/Shared/ByteArrayBuffer.h index 50a22f8d9bf..1ef740cfd1c 100644 --- a/vnext/JSI/Shared/ByteArrayBuffer.h +++ b/vnext/JSI/Shared/ByteArrayBuffer.h @@ -3,9 +3,10 @@ #pragma once -#include #include "jsi/jsi.h" +#include + namespace Microsoft::JSI { class ByteArrayBuffer final : public facebook::jsi::Buffer { diff --git a/vnext/JSI/Shared/ChakraObjectRef.cpp b/vnext/JSI/Shared/ChakraObjectRef.cpp new file mode 100644 index 00000000000..1772905dd41 --- /dev/null +++ b/vnext/JSI/Shared/ChakraObjectRef.cpp @@ -0,0 +1,322 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "ChakraObjectRef.h" + +#include "Unicode.h" +#include "Utilities.h" + +#include "jsi//jsi.h" + +#include +#include +#include + +namespace Microsoft::JSI { + +void VerifyChakraErrorElseThrow(JsErrorCode error) { + if (error != JsNoError) { + std::ostringstream errorString; + errorString << "A call to Chakra(Core) API returned error code 0x" + << std::hex << error << '.'; + throw facebook::jsi::JSINativeException(errorString.str()); + } +} + +ChakraObjectRef::ChakraObjectRef(const ChakraObjectRef &original) noexcept + : m_ref{original.m_ref}, m_state{original.m_state} { + if (m_state == State::Initialized) { + VerifyChakraErrorElseThrow(JsAddRef(m_ref, nullptr)); + } +} + +ChakraObjectRef::ChakraObjectRef(ChakraObjectRef &&original) noexcept { + Swap(original); +} + +ChakraObjectRef &ChakraObjectRef::operator=( + const ChakraObjectRef &rhs) noexcept { + ChakraObjectRef rhsCopy(rhs); + Swap(rhsCopy); + return *this; +} + +ChakraObjectRef &ChakraObjectRef::operator=(ChakraObjectRef &&rhs) noexcept { + Swap(rhs); + return *this; +} + +ChakraObjectRef::~ChakraObjectRef() noexcept { + if (m_state == State::Initialized) { + VerifyChakraErrorElseThrow(JsRelease(m_ref, nullptr)); + } +} + +void ChakraObjectRef::Initialize(JsRef ref) { + if (m_state != State::Uninitialized) { + throw facebook::jsi::JSINativeException( + "A ChakraObjectRef can only be initialzed once."); + } + + if (!ref) { + throw facebook::jsi::JSINativeException( + "Cannot initialize a ChakraObjectRef with a null reference."); + } + + VerifyChakraErrorElseThrow(JsAddRef(ref, nullptr)); + m_ref = ref; + m_state = State::Initialized; +} + +void ChakraObjectRef::Invalidate() { + switch (m_state) { + case State::Uninitialized: { + throw facebook::jsi::JSINativeException( + "Cannot invalidate a ChakraObjectRef that has not been initialized."); + break; + } + case State::Initialized: { + VerifyChakraErrorElseThrow(JsRelease(m_ref, nullptr)); + m_ref = nullptr; + m_state = State::Invalidated; + break; + } + case State::Invalidated: { + throw facebook::jsi::JSINativeException( + "Cannot invalidate a ChakraObjectRef that has already been " + "invalidated."); + break; + } + default: { + // Control flow should never reach here. + std::terminate(); + break; + } + } +} + +void ChakraObjectRef::Swap(ChakraObjectRef &other) { + std::swap(m_ref, other.m_ref); + std::swap(m_state, other.m_state); +} + +JsValueType GetValueType(const ChakraObjectRef &jsValue) { + JsValueType type; + VerifyChakraErrorElseThrow(JsGetValueType(jsValue, &type)); + return type; +} + +JsPropertyIdType GetPropertyIdType(const ChakraObjectRef &jsPropId) { + JsPropertyIdType type; + VerifyChakraErrorElseThrow(JsGetPropertyIdType(jsPropId, &type)); + return type; +} + +std::wstring GetPropertyName(const ChakraObjectRef &id) { + if (GetPropertyIdType(id) != JsPropertyIdTypeString) { + throw facebook::jsi::JSINativeException( + "It is llegal to retrieve the name of a property symbol."); + } + const wchar_t *propertyName = nullptr; + VerifyChakraErrorElseThrow(JsGetPropertyNameFromId(id, &propertyName)); + return std::wstring{propertyName}; +} + +ChakraObjectRef GetPropertySymbol(const ChakraObjectRef &id) { + if (GetPropertyIdType(id) != JsPropertyIdTypeSymbol) { + throw facebook::jsi::JSINativeException( + "It is llegal to retrieve the symbol associated with a property name."); + } + JsValueRef symbol = nullptr; + VerifyChakraErrorElseThrow(JsGetSymbolFromPropertyId(id, &symbol)); + return ChakraObjectRef{symbol}; +} + +ChakraObjectRef GetPropertyId(const std::string_view &utf8) { + if (!utf8.data()) { + throw facebook::jsi::JSINativeException( + "Property name cannot be a nullptr."); + } + + // We use a #ifdef here because we can avoid a UTF-8 to UTF-16 conversion + // using ChakraCore's JsCreatePropertyId API. +#ifdef CHAKRACORE + JsPropertyIdRef id = nullptr; + VerifyChakraErrorElseThrow( + JsCreatePropertyId(utf8.data(), utf8.length(), &id)); + return ChakraObjectRef(id); + +#else + std::wstring utf16 = Common::Unicode::Utf8ToUtf16(utf8.data(), utf8.length()); + return GetPropertyId(utf16); +#endif +} + +ChakraObjectRef GetPropertyId(const std::wstring &utf16) { + JsPropertyIdRef id = nullptr; + VerifyChakraErrorElseThrow(JsGetPropertyIdFromName(utf16.c_str(), &id)); + return ChakraObjectRef(id); +} + +std::string ToStdString(const ChakraObjectRef &jsString) { + if (GetValueType(jsString) != JsString) { + throw facebook::jsi::JSINativeException( + "Cannot convert a non JS string ChakraObjectRef to a std::string."); + } + + // We use a #ifdef here because we can avoid a UTF-8 to UTF-16 conversion + // using ChakraCore's JsCopyString API. +#ifdef CHAKRACORE + size_t length = 0; + VerifyChakraErrorElseThrow(JsCopyString(jsString, nullptr, 0, &length)); + + std::string result(length, 'a'); + VerifyChakraErrorElseThrow( + JsCopyString(jsString, result.data(), result.length(), &length)); + + if (length != result.length()) { + throw facebook::jsi::JSINativeException( + "Failed to convert a JS string to a std::string."); + } + return result; + +#else + return Common::Unicode::Utf16ToUtf8(ToStdWstring(jsString)); +#endif +} + +std::wstring ToStdWstring(const ChakraObjectRef &jsString) { + if (GetValueType(jsString) != JsString) { + throw facebook::jsi::JSINativeException( + "Cannot convert a non JS string ChakraObjectRef to a std::wstring."); + } + + const wchar_t *utf16 = nullptr; + size_t length = 0; + VerifyChakraErrorElseThrow(JsStringToPointer(jsString, &utf16, &length)); + + return std::wstring(utf16, length); +} + +ChakraObjectRef ToJsString(const std::string_view &utf8) { + if (!utf8.data()) { + throw facebook::jsi::JSINativeException( + "Cannot convert a nullptr to a JS string."); + } + + // We use a #ifdef here because we can avoid a UTF-8 to UTF-16 conversion + // using ChakraCore's JsCreateString API. +#ifdef CHAKRACORE + JsValueRef result = nullptr; + VerifyChakraErrorElseThrow( + JsCreateString(utf8.data(), utf8.length(), &result)); + return ChakraObjectRef(result); + +#else + std::wstring utf16 = Common::Unicode::Utf8ToUtf16(utf8.data(), utf8.length()); + return ToJsString(std::wstring_view{utf16.c_str(), utf16.length()}); +#endif +} + +ChakraObjectRef ToJsString(const std::wstring_view &utf16) { + if (!utf16.data()) { + throw facebook::jsi::JSINativeException( + "Cannot convert a nullptr to a JS string."); + } + + JsValueRef result = nullptr; + VerifyChakraErrorElseThrow( + JsPointerToString(utf16.data(), utf16.length(), &result)); + + return ChakraObjectRef(result); +} + +ChakraObjectRef ToJsString(const ChakraObjectRef &ref) { + JsValueRef str = nullptr; + VerifyChakraErrorElseThrow(JsConvertValueToString(ref, &str)); + return ChakraObjectRef(str); +} + +ChakraObjectRef ToJsNumber(int num) { + JsValueRef result = nullptr; + VerifyChakraErrorElseThrow(JsIntToNumber(num, &result)); + return ChakraObjectRef(result); +} + +ChakraObjectRef ToJsArrayBuffer( + const std::shared_ptr &buffer) { + if (!buffer) { + throw facebook::jsi::JSINativeException( + "Cannot create an external JS ArrayBuffer without backing buffer."); + } + + size_t size = buffer->size(); + + if (size > UINT_MAX) { + throw facebook::jsi::JSINativeException( + "The external backing buffer for a JS ArrayBuffer is too large."); + } + + JsValueRef arrayBuffer = nullptr; + auto bufferWrapper = + std::make_unique>(buffer); + + // We allocate a copy of buffer on the heap, a shared_ptr which is deleted + // when the JavaScript garbage collecotr releases the created external array + // buffer. This ensures that buffer stays alive while the JavaScript engine is + // using it. + VerifyChakraErrorElseThrow(JsCreateExternalArrayBuffer( + Common::Utilities::CheckedReinterpretCast( + const_cast(buffer->data())), + static_cast(size), + [](void *bufferToDestroy) { + // We wrap bufferToDestroy in a unique_ptr to avoid calling delete + // explicitly. + std::unique_ptr> wrapper{ + static_cast *>( + bufferToDestroy)}; + }, + bufferWrapper.get(), + &arrayBuffer)); + + // We only call bufferWrapper.release() after JsCreateExternalObject succeeds. + // Otherwise, when JsCreateExternalObject fails and an exception is thrown, + // the shared_ptr that bufferWrapper used to own will be leaked. + bufferWrapper.release(); + return ChakraObjectRef(arrayBuffer); +} + +bool CompareJsValues( + const ChakraObjectRef &jsValue1, + const ChakraObjectRef &jsValue2) { + bool result = false; + // Note that JsStrictEquals should only be used for JsValueRefs and not for + // other types of JsRefs (e.g. JsPropertyIdRef, etc.). + VerifyChakraErrorElseThrow(JsStrictEquals(jsValue1, jsValue2, &result)); + return result; +} + +bool CompareJsPropertyIds( + const ChakraObjectRef &jsPropId1, + const ChakraObjectRef &jsPropId2) { + JsPropertyIdType type1 = GetPropertyIdType(jsPropId1); + JsPropertyIdType type2 = GetPropertyIdType(jsPropId2); + + if (type1 != type2) { + return false; + } + + if (type1 == JsPropertyIdTypeString) { + return GetPropertyName(jsPropId1) == GetPropertyName(jsPropId2); + } + + if (type1 == JsPropertyIdTypeSymbol) { + return CompareJsValues( + GetPropertySymbol(jsPropId1), GetPropertySymbol(jsPropId2)); + } + + // Control should never reach here. + std::terminate(); +} + +} // namespace Microsoft::JSI diff --git a/vnext/JSI/Shared/ChakraObjectRef.h b/vnext/JSI/Shared/ChakraObjectRef.h new file mode 100644 index 00000000000..10243499e75 --- /dev/null +++ b/vnext/JSI/Shared/ChakraObjectRef.h @@ -0,0 +1,224 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include "jsi/jsi.h" + +#ifdef CHAKRACORE +#include "ChakraCore.h" +#else +#ifndef USE_EDGEMODE_JSRT +#define USE_EDGEMODE_JSRT +#endif +#include +#endif + +#include +#include +#include + +namespace Microsoft::JSI { + +inline void VerifyChakraErrorElseCrash(JsErrorCode error) { + if (error != JsNoError) { + std::terminate(); + } +} + +void VerifyChakraErrorElseThrow(JsErrorCode error); + +/** + * @brief An shared_ptr like RAII Wrapper for JsRefs. + * + * JsRefs are references to objects owned by the garbage collector and include + * JsContextRef, JsValueRef, and JsPropertyIdRef, etc. ChakraObjectRef ensures + * that JsAddRef and JsRelease are called upon initialization and invalidation, + * respectively. It also allows users to implicitly convert it into a JsRef. A + * ChakraObjectRef must only be initialized once and invalidated once. + */ +class ChakraObjectRef { + public: + ChakraObjectRef() noexcept {} + inline explicit ChakraObjectRef(JsRef ref) { + Initialize(ref); + } + + ChakraObjectRef(const ChakraObjectRef &original) noexcept; + ChakraObjectRef(ChakraObjectRef &&original) noexcept; + + ChakraObjectRef &operator=(const ChakraObjectRef &rhs) noexcept; + ChakraObjectRef &operator=(ChakraObjectRef &&rhs) noexcept; + + ~ChakraObjectRef() noexcept; + + void Initialize(JsRef ref); + void Invalidate(); + + inline operator JsRef() const noexcept { + return m_ref; + } + + private: + void Swap(ChakraObjectRef &other); + + enum class State { Uninitialized, Initialized, Invalidated }; + + JsRef m_ref = nullptr; + State m_state = State::Uninitialized; +}; + +/** + * @param jsValue A ChakraObjectRef managing a JsValueRef. + */ +JsValueType GetValueType(const ChakraObjectRef &jsValue); + +/** + * @param jsPropId A ChakraObjectRef managing a JsPropertyIdRef. + */ +JsPropertyIdType GetPropertyIdType(const ChakraObjectRef &jsPropId); + +/** + * @param jsPropId A ChakraObjectRef managing a JsPropertyIdRef of type + * JsPropertyIdTypeString. + */ +std::wstring GetPropertyName(const ChakraObjectRef &jsPropId); + +/** + * @param jsPropId A ChakraObjectRef managing a JsPropertyIdRef of type + * JsPropertyIdTypeSymbol. + * + * @returns A ChakraObjectRef managing a JS Symbol. + */ +ChakraObjectRef GetPropertySymbol(const ChakraObjectRef &jsPropId); + +/** + * @param utf8 A std::string_view to a UTF-8 encoded char array. + * + * @returns A ChakraObjectRef managing a JsPropertyIdRef. + */ +ChakraObjectRef GetPropertyId(const std::string_view &utf8); + +/** + * @param utf16 A UTF-16 encoded std::wstring. + * + * @returns A ChakraObjectRef managing a JsPropertyIdRef. + */ +ChakraObjectRef GetPropertyId(const std::wstring &utf16); + +/** + * @param jsString A ChakraObjectRef managing a JS string. + * + * @returns A std::string that is UTF-8 encoded. + * + * @remarks This function copies the JS string buffer into the returned + * std::string. When using Chakra instead of ChakraCore, this function incurs + * a UTF-16 to UTF-8 conversion. + */ +std::string ToStdString(const ChakraObjectRef &jsString); + +/** + * @param jsString A ChakraObjectRef managing a JS string. + * + * @returns A std::wstring that is UTF-16 encoded. + * + * @remarks This functions copies the JS string buffer into the returned + * std::wstring. + */ +std::wstring ToStdWstring(const ChakraObjectRef &jsString); + +/** + * @param utf8 A std::string_view to a UTF-8 encoded char array. + * + * @returns A ChakraObjectRef managing a JS string. + * + * @remarks The content of utf8 is copied into JS engine owned memory. When + * using Chakra instead of ChakraCore, this function incurs a UTF-8 to UTF-16 + * conversion. + */ +ChakraObjectRef ToJsString(const std::string_view &utf8); + +/** + * @param utf16 A std::wstring_view to a UTF-16 encoded wchar_t array. + * + * @returns A ChakraObjectRef managing a JS string. + * + * @remarks The content of utf16 is copied into JS engine owned memory. + */ +ChakraObjectRef ToJsString(const std::wstring_view &utf16); + +/** + * @param jsValue A ChakraObjectRef mananing a JsValueRef. + * + * @returns A ChakraObjectRef managing the return value of the JS .toString + * function. + */ +ChakraObjectRef ToJsString(const ChakraObjectRef &jsValue); + +/** + * @returns A ChakraObjectRef managing a JS number. + */ +ChakraObjectRef ToJsNumber(int num); + +/** + * @returns A ChakraObjectRef managing a JS Object. + * + * @remarks The returned Object is backed by data and keeps data alive till the + * garbage collector finalizes it. + */ +template +ChakraObjectRef ToJsObject(std::unique_ptr &&data) { + if (!data) { + throw facebook::jsi::JSINativeException( + "Cannot create an external JS Object without backing data."); + } + + JsValueRef obj = nullptr; + VerifyChakraErrorElseThrow(JsCreateExternalObject( + data.get(), + [](void *dataToDestroy) { + // We wrap dataToDestroy in a unique_ptr to avoid calling delete + // explicitly. + std::unique_ptr wrapper{static_cast(dataToDestroy)}; + }, + &obj)); + + // We only call data.release() after JsCreateExternalObject succeeds. + // Otherwise, when JsCreateExternalObject fails and an exception is thrown, + // the buffer that data used to own will be leaked. + data.release(); + return ChakraObjectRef(obj); +} + +/** + * @returns A ChakraObjectRef managing a JS ArrayBuffer. + * + * @remarks The returned ArrayBuffer is backed by buffer and keeps buffer alive + * till the garbage collector finalizes it. + */ +ChakraObjectRef ToJsArrayBuffer( + const std::shared_ptr &buffer); + +/** + * @param jsValue1 A ChakraObjectRef managing a JsValueRef. + * @param jsValue2 A ChakraObjectRef managing a JsValueRef. + * + * @returns A boolean indicating whether jsValue1 and jsValue2 are strictly + * equal. + */ +bool CompareJsValues( + const ChakraObjectRef &jsValue1, + const ChakraObjectRef &jsValue2); + +/** + * @param jsPropId1 A ChakraObjectRef managing a JsPropertyIdRef. + * @param jsPropId2 A ChakraObjectRef managing a JsPropertyIdRef. + * + * @returns A boolean indicating whether jsPropId1 and jsPropId2 are strictly + * equal. + */ +bool CompareJsPropertyIds( + const ChakraObjectRef &jsPropId1, + const ChakraObjectRef &jsPropId2); + +} // namespace Microsoft::JSI diff --git a/vnext/JSI/Shared/ChakraRuntime.cpp b/vnext/JSI/Shared/ChakraRuntime.cpp index 983d92df414..179dee3145f 100644 --- a/vnext/JSI/Shared/ChakraRuntime.cpp +++ b/vnext/JSI/Shared/ChakraRuntime.cpp @@ -2,18 +2,17 @@ // Licensed under the MIT License. #include "ChakraRuntime.h" -#include "ChakraRuntimeArgs.h" + +#include "Unicode.h" +#include "Utilities.h" #include #include +#include +#include #include #include -#include "Unicode.h" - -#include - -using namespace facebook::react; namespace Microsoft::JSI { @@ -39,41 +38,18 @@ class HostFunctionProxy { ChakraRuntime &m_runtime; }; -class ArgsConverterForCall { - public: - ArgsConverterForCall( - ChakraRuntime &rt, - JsValueRef thisObj, - const facebook::jsi::Value *args, - size_t count) { - JsValueRef *destination = inline_; - if (count + 1 > maxStackArgs) { - outOfLine_ = std::make_unique(count + 1); - destination = outOfLine_.get(); - } - - if (thisObj == nullptr) { - JsValueRef undefinedValue; - JsGetUndefinedValue(&undefinedValue); - destination[0] = undefinedValue; - } else { - destination[0] = thisObj; - } - - for (size_t i = 0; i < count; ++i) { - destination[i + 1] = rt.valueRef(args[i]); - } - } - - operator JsValueRef *() { - return outOfLine_ ? outOfLine_.get() : inline_; +// Callers of this functions must make sure that jsThis and args are alive when +// using the return value of this function. +std::vector constructJsFunctionArguments( + const ChakraObjectRef &jsThis, + const std::vector &args) { + std::vector result; + result.push_back(JsRef(jsThis)); + for (const ChakraObjectRef &ref : args) { + result.push_back(JsRef(ref)); } - - private: - constexpr static unsigned maxStackArgs = 8; - JsValueRef inline_[maxStackArgs]; - std::unique_ptr outOfLine_; -}; + return result; +} } // namespace @@ -81,25 +57,24 @@ ChakraRuntime::ChakraRuntime(ChakraRuntimeArgs &&args) noexcept : m_args{std::move(args)} { JsRuntimeAttributes runtimeAttributes = JsRuntimeAttributeNone; - if (!runtimeArgs().enableJITCompilation) { + if (!m_args.enableJITCompilation) { runtimeAttributes = static_cast( runtimeAttributes | JsRuntimeAttributeDisableNativeCodeGeneration | JsRuntimeAttributeDisableExecutablePageAllocation); } - if (JsCreateRuntime(runtimeAttributes, nullptr, &m_runtime) != JsNoError) { - std::terminate(); - } + VerifyChakraErrorElseThrow( + JsCreateRuntime(runtimeAttributes, nullptr, &m_runtime)); setupMemoryTracker(); - // Create an execution context - JsCreateContext(m_runtime, &m_ctx); - JsAddRef(m_ctx, nullptr); + JsContextRef context = nullptr; + VerifyChakraErrorElseThrow(JsCreateContext(m_runtime, &context)); + m_context.Initialize(context); // Note :: We currently assume that the runtime will be created and // exclusively used in a single thread. - JsSetCurrentContext(m_ctx); + VerifyChakraErrorElseThrow(JsSetCurrentContext(m_context)); startDebuggingIfNeeded(); @@ -111,12 +86,12 @@ ChakraRuntime::ChakraRuntime(ChakraRuntimeArgs &&args) noexcept ChakraRuntime::~ChakraRuntime() noexcept { stopDebuggingIfNeeded(); - JsSetCurrentContext(JS_INVALID_REFERENCE); - JsRelease(m_ctx, nullptr); + VerifyChakraErrorElseThrow(JsSetCurrentContext(JS_INVALID_REFERENCE)); + m_context.Invalidate(); JsSetRuntimeMemoryAllocationCallback(m_runtime, nullptr, nullptr); - JsDisposeRuntime(m_runtime); + VerifyChakraErrorElseThrow(JsDisposeRuntime(m_runtime)); } #pragma region Functions_inherited_from_Runtime @@ -214,9 +189,9 @@ facebook::jsi::Value ChakraRuntime::evaluatePreparedJavaScript( } facebook::jsi::Object ChakraRuntime::global() { - JsValueRef value; - JsGetGlobalObject(&value); - return createObject(value); + JsValueRef global; + VerifyJsErrorElseThrow(JsGetGlobalObject(&global)); + return MakePointer(global); } std::string ChakraRuntime::description() { @@ -228,115 +203,94 @@ bool ChakraRuntime::isInspectable() { } facebook::jsi::Runtime::PointerValue *ChakraRuntime::cloneSymbol( - const facebook::jsi::Runtime::PointerValue *) { - throw facebook::jsi::JSINativeException("Not implemented!"); + const facebook::jsi::Runtime::PointerValue *pointerValue) { + return CloneChakraPointerValue(pointerValue); } facebook::jsi::Runtime::PointerValue *ChakraRuntime::cloneString( - const facebook::jsi::Runtime::PointerValue *pv) { - if (!pv) { - return nullptr; - } - const ChakraStringValue *string = static_cast(pv); - return makeStringValue(string->m_str); + const facebook::jsi::Runtime::PointerValue *pointerValue) { + return CloneChakraPointerValue(pointerValue); } facebook::jsi::Runtime::PointerValue *ChakraRuntime::cloneObject( - const facebook::jsi::Runtime::PointerValue *pv) { - if (!pv) { - return nullptr; - } - - const ChakraObjectValue *object = static_cast(pv); - return makeObjectValue(object->m_obj); + const facebook::jsi::Runtime::PointerValue *pointerValue) { + return CloneChakraPointerValue(pointerValue); } facebook::jsi::Runtime::PointerValue *ChakraRuntime::clonePropNameID( - const facebook::jsi::Runtime::PointerValue *pv) { - if (!pv) { - return nullptr; - } - - const ChakraPropertyIdValue *propId = - static_cast(pv); - return makePropertyIdValue(propId->m_propId); + const facebook::jsi::Runtime::PointerValue *pointerValue) { + return CloneChakraPointerValue(pointerValue); } facebook::jsi::PropNameID ChakraRuntime::createPropNameIDFromAscii( const char *str, size_t length) { - JsValueRef propIdRef = createJSPropertyId(str, length); - - auto res = createPropNameID(propIdRef); - return res; + ChakraObjectRef propId = GetPropertyId(std::string_view{str, length}); + return MakePointer(std::move(propId)); } facebook::jsi::PropNameID ChakraRuntime::createPropNameIDFromUtf8( const uint8_t *utf8, size_t length) { - JsValueRef prpoIdRef = - createJSPropertyId(reinterpret_cast(utf8), length); - - auto res = createPropNameID(prpoIdRef); - return res; + return createPropNameIDFromAscii( + Common::Utilities::CheckedReinterpretCast(utf8), length); } facebook::jsi::PropNameID ChakraRuntime::createPropNameIDFromString( const facebook::jsi::String &str) { - std::string propNameString = JSStringToSTLString(stringRef(str)); - return createPropNameIDFromUtf8( - reinterpret_cast(propNameString.c_str()), - propNameString.length()); + // We don not use the functions: + // std::string ChakraRuntime::utf8(const String& str), and + // std::string ToStdString(const ChakraObjectRef &jsString) + // here to avoud excessive Unicode conversions. + std::wstring propName = ToStdWstring(GetChakraObjectRef(str)); + return MakePointer(GetPropertyId(propName)); } -std::string ChakraRuntime::utf8(const facebook::jsi::PropNameID &sym) { - const wchar_t *name; - checkException(JsGetPropertyNameFromId(propIdRef(sym), &name)); - return Microsoft::Common::Unicode::Utf16ToUtf8(name, wcslen(name)); +std::string ChakraRuntime::utf8(const facebook::jsi::PropNameID &id) { + return Common::Unicode::Utf16ToUtf8(GetPropertyName(GetChakraObjectRef(id))); } bool ChakraRuntime::compare( - const facebook::jsi::PropNameID &a, - const facebook::jsi::PropNameID &b) { - bool result; - JsEquals(propIdRef(a), propIdRef(b), &result); - return result; + const facebook::jsi::PropNameID &lhs, + const facebook::jsi::PropNameID &rhs) { + return CompareJsPropertyIds(GetChakraObjectRef(lhs), GetChakraObjectRef(rhs)); } -std::string ChakraRuntime::symbolToString(const facebook::jsi::Symbol &) { - throw facebook::jsi::JSINativeException("Not implemented!"); +std::string ChakraRuntime::symbolToString(const facebook::jsi::Symbol &s) { + return ToStdString(ToJsString(GetChakraObjectRef(s))); } facebook::jsi::String ChakraRuntime::createStringFromAscii( const char *str, size_t length) { - // Yes we end up double casting for semantic reasons (UTF8 contains ASCII, - // not the other way around) - return this->createStringFromUtf8( - reinterpret_cast(str), length); + // Unfortunately due to the typing used by JSI and Chakra, we have to do a + // double reinterpret cast here. + return createStringFromUtf8( + Common::Utilities::CheckedReinterpretCast(str), length); } facebook::jsi::String ChakraRuntime::createStringFromUtf8( const uint8_t *str, size_t length) { - JsValueRef stringRef = - createJSString(reinterpret_cast(str), length); - return createString(stringRef); + return MakePointer(ToJsString(std::string_view{ + Common::Utilities::CheckedReinterpretCast(str), length})); } std::string ChakraRuntime::utf8(const facebook::jsi::String &str) { - return JSStringToSTLString(stringRef(str)); + return ToStdString(GetChakraObjectRef(str)); } facebook::jsi::Object ChakraRuntime::createObject() { - return createObject(static_cast(nullptr)); + JsValueRef obj; + VerifyJsErrorElseThrow(JsCreateObject(&obj)); + return MakePointer(obj); } facebook::jsi::Object ChakraRuntime::createObject( std::shared_ptr hostObject) { facebook::jsi::Object proxyTarget = ObjectWithExternalData::create( - *this, new HostObjectProxy(*this, hostObject)); + *this, std::make_unique(*this, hostObject)); return createProxy(std::move(proxyTarget), createHostObjectProxyHandler()); } @@ -370,84 +324,66 @@ facebook::jsi::HostFunctionType &ChakraRuntime::getHostFunction( facebook::jsi::Value ChakraRuntime::getProperty( const facebook::jsi::Object &obj, - const facebook::jsi::String &name) { - JsValueRef objRef = objectRef(obj); - - std::wstring propName = JSStringToSTLWString(stringRef(name)); - JsPropertyIdRef propIdRef; - checkException(JsGetPropertyIdFromName(propName.c_str(), &propIdRef)); - - JsValueRef value; - checkException(JsGetProperty(objectRef(obj), propIdRef, &value)); - return createValue(value); + const facebook::jsi::PropNameID &name) { + JsValueRef result = nullptr; + VerifyJsErrorElseThrow(JsGetProperty( + GetChakraObjectRef(obj), GetChakraObjectRef(name), &result)); + return ToJsiValue(ChakraObjectRef(result)); } facebook::jsi::Value ChakraRuntime::getProperty( const facebook::jsi::Object &obj, - const facebook::jsi::PropNameID &name) { - JsValueRef objRef = objectRef(obj); - JsValueRef exc = nullptr; - JsValueRef res; - checkException(JsGetProperty(objRef, propIdRef(name), &res)); - return createValue(res); + const facebook::jsi::String &name) { + return getProperty(obj, createPropNameIDFromString(name)); } bool ChakraRuntime::hasProperty( const facebook::jsi::Object &obj, - const facebook::jsi::String &name) { - std::wstring propName = JSStringToSTLWString(stringRef(name)); - - JsPropertyIdRef propId = JS_INVALID_REFERENCE; - checkException(JsGetPropertyIdFromName(propName.c_str(), &propId)); - - bool hasProperty; - checkException(JsHasProperty(objectRef(obj), propId, &hasProperty)); - return hasProperty; + const facebook::jsi::PropNameID &name) { + bool result = false; + VerifyJsErrorElseThrow(JsHasProperty( + GetChakraObjectRef(obj), GetChakraObjectRef(name), &result)); + return result; } bool ChakraRuntime::hasProperty( const facebook::jsi::Object &obj, - const facebook::jsi::PropNameID &name) { - bool hasProperty; - checkException(JsHasProperty(objectRef(obj), propIdRef(name), &hasProperty)); - return hasProperty; + const facebook::jsi::String &name) { + return hasProperty(obj, createPropNameIDFromString(name)); } void ChakraRuntime::setPropertyValue( facebook::jsi::Object &object, const facebook::jsi::PropNameID &name, const facebook::jsi::Value &value) { - checkException( - JsSetProperty(objectRef(object), propIdRef(name), valueRef(value), true)); + // We use strict rules for property assignments here, so any assignment that + // silently fails in normal code (assignment to a non-writable global or + // property, assignment to a getter-only property, assignment to a new + // property on a non-extensible object) will throw. + VerifyJsErrorElseThrow(JsSetProperty( + GetChakraObjectRef(object), + GetChakraObjectRef(name), + ToChakraObjectRef(value), + true /* useStrictRules */)); } void ChakraRuntime::setPropertyValue( facebook::jsi::Object &object, const facebook::jsi::String &name, const facebook::jsi::Value &value) { - std::wstring propName = JSStringToSTLWString(stringRef(name)); - - JsPropertyIdRef propId = JS_INVALID_REFERENCE; - checkException(JsGetPropertyIdFromName(propName.c_str(), &propId)); - - checkException( - JsSetProperty(objectRef(object), propId, valueRef(value), true)); + setPropertyValue(object, createPropNameIDFromString(name), value); } -bool ChakraRuntime::isArray(const facebook::jsi::Object &obj) const { - JsValueType type; - JsGetValueType(objectRef(obj), &type); - return type == JsValueType::JsArray; +bool ChakraRuntime::isArray(const facebook::jsi::Object &object) const { + return GetValueType(GetChakraObjectRef(object)) == JsArray; } -bool ChakraRuntime::isArrayBuffer(const facebook::jsi::Object & /*obj*/) const { - throw std::runtime_error("Unsupported"); +bool ChakraRuntime::isArrayBuffer(const facebook::jsi::Object &object) const { + return GetValueType(GetChakraObjectRef(object)) == JsArrayBuffer; } bool ChakraRuntime::isFunction(const facebook::jsi::Object &obj) const { - JsValueType type; - JsGetValueType(objectRef(obj), &type); - return type == JsValueType::JsFunction; + return GetValueType(GetChakraObjectRef(obj)) == JsFunction; } bool ChakraRuntime::isHostObject(const facebook::jsi::Object &obj) const { @@ -464,91 +400,124 @@ bool ChakraRuntime::isHostFunction(const facebook::jsi::Function &obj) const { } facebook::jsi::Array ChakraRuntime::getPropertyNames( - const facebook::jsi::Object &obj) { - JsValueRef propertyNamesArrayRef; - checkException(JsGetOwnPropertyNames(objectRef(obj), &propertyNamesArrayRef)); - - JsPropertyIdRef propertyId; - checkException(JsGetPropertyIdFromName(L"length", &propertyId)); - JsValueRef countRef; - checkException(JsGetProperty(propertyNamesArrayRef, propertyId, &countRef)); - int count; - checkException(JsNumberToInt(countRef, &count)); - - auto result = createArray(count); - for (int i = 0; i < count; i++) { - JsValueRef index; - checkException(JsIntToNumber(i, &index)); - JsValueRef propertyName; - checkException( - JsGetIndexedProperty(propertyNamesArrayRef, index, &propertyName)); - result.setValueAtIndex(*this, i, createString(propertyName)); - } - - return result; -} + const facebook::jsi::Object &object) { + constexpr const char *const jsGetPropertyNamesSource = + "(function()\n" + "{\n" + " return function(obj)\n" + " {\n" + " var propertyNames = []\n" + " for (propertyName in obj) \n" + " {\n" + " propertyNames.push(propertyName)\n" + " }\n" + " return propertyNames\n" + " }\n" + "})()"; + + static facebook::jsi::StringBuffer jsGetPropertyNamesSourceBuffer{ + jsGetPropertyNamesSource}; + + facebook::jsi::Function jsGetPropertyNames = + evaluateJavaScriptSimple(jsGetPropertyNamesSourceBuffer, "") + .asObject(*this) + .asFunction(*this); + + facebook::jsi::Value objAsValue(*this, object); + return call( + jsGetPropertyNames, + facebook::jsi::Value::undefined(), + &objAsValue, + 1) + .asObject(*this) + .asArray(*this); +} + +// Only ChakraCore supports weak reference semantics, so ChakraRuntime +// WeakObjects are in fact strong references. facebook::jsi::WeakObject ChakraRuntime::createWeakObject( - const facebook::jsi::Object &obj) { + const facebook::jsi::Object &object) { return make( - makeWeakRefValue(newWeakObjectRef(obj))); + CloneChakraPointerValue(getPointerValue(object))); } facebook::jsi::Value ChakraRuntime::lockWeakObject( - const facebook::jsi::WeakObject &weakObj) { - return createValue(strongObjectRef(weakObj)); + const facebook::jsi::WeakObject &weakObject) { + // We need to make a copy of the ChakraObjectRef held within weakObj's + // member PointerValue for the returned jsi::Value here. + ChakraObjectRef ref = GetChakraObjectRef(weakObject); + return ToJsiValue(std::move(ref)); } facebook::jsi::Array ChakraRuntime::createArray(size_t length) { - JsValueRef result; - checkException(JsCreateArray(static_cast(length), &result)); - return createObject(result).getArray(*this); + assert(length <= UINT_MAX); + JsValueRef result = nullptr; + + VerifyJsErrorElseThrow( + JsCreateArray(static_cast(length), &result)); + + return MakePointer(result).asArray(*this); } size_t ChakraRuntime::size(const facebook::jsi::Array &arr) { - std::string lengthStr = "length"; + assert(isArray(arr)); - JsPropertyIdRef propId = - createJSPropertyId(lengthStr.c_str(), lengthStr.length()); + constexpr const uint8_t propName[] = {'l', 'e', 'n', 'g', 't', 'h'}; - JsValueRef valueObject; - checkException(JsGetProperty(objectRef(arr), propId, &valueObject)); + facebook::jsi::PropNameID propId = createPropNameIDFromUtf8( + propName, Common::Utilities::ArraySize(propName)); - JsValueRef numberValue; - checkException(JsConvertValueToNumber(valueObject, &numberValue)); + return static_cast(getProperty(arr, propId).asNumber()); +} - int intValue; - checkException(JsNumberToInt(numberValue, &intValue)); +uint8_t *ChakraRuntime::data(const facebook::jsi::ArrayBuffer &arrBuf) { + assert(isArrayBuffer(arrBuf)); - return intValue; -} + uint8_t *buffer = nullptr; + unsigned int size = 0; -uint8_t *ChakraRuntime::data(const facebook::jsi::ArrayBuffer & /*obj*/) { - throw std::runtime_error("Unsupported"); + VerifyJsErrorElseThrow( + JsGetArrayBufferStorage(GetChakraObjectRef(arrBuf), &buffer, &size)); + + return buffer; } -size_t ChakraRuntime::size(const facebook::jsi::ArrayBuffer & /*obj*/) { - throw std::runtime_error("Unsupported"); +size_t ChakraRuntime::size(const facebook::jsi::ArrayBuffer &arrBuf) { + assert(isArrayBuffer(arrBuf)); + + constexpr const uint8_t propName[] = { + 'b', 'y', 't', 'e', 'l', 'e', 'n', 'g', 't', 'h'}; + + facebook::jsi::PropNameID propId = createPropNameIDFromUtf8( + propName, Common::Utilities::ArraySize(propName)); + + return static_cast(getProperty(arrBuf, propId).asNumber()); } facebook::jsi::Value ChakraRuntime::getValueAtIndex( const facebook::jsi::Array &arr, - size_t i) { - JsValueRef index; - JsIntToNumber(static_cast(i), &index); - JsValueRef property; - checkException(JsGetIndexedProperty(objectRef(arr), index, &property)); - return createValue(property); + size_t index) { + assert(isArray(arr)); + assert(index <= INT_MAX); + + JsValueRef result = nullptr; + VerifyJsErrorElseThrow(JsGetIndexedProperty( + GetChakraObjectRef(arr), ToJsNumber(static_cast(index)), &result)); + return ToJsiValue(ChakraObjectRef(result)); } void ChakraRuntime::setValueAtIndexImpl( facebook::jsi::Array &arr, - size_t i, + size_t index, const facebook::jsi::Value &value) { - JsValueRef index; - JsIntToNumber(static_cast(i), &index); + assert(isArray(arr)); + assert(index <= INT_MAX); - checkException(JsSetIndexedProperty(objectRef(arr), index, valueRef(value))); + VerifyJsErrorElseThrow(JsSetIndexedProperty( + GetChakraObjectRef(arr), + ToJsNumber(static_cast(index)), + ToChakraObjectRef(value))); } facebook::jsi::Function ChakraRuntime::createFunctionFromHostFunction( @@ -557,48 +526,65 @@ facebook::jsi::Function ChakraRuntime::createFunctionFromHostFunction( facebook::jsi::HostFunctionType func) { // Currently, we are allocating this proxy object in heap .. and deleting it // whenever the JS object is garbage collected. + // TODO (yicyao): We should get rid of these naked new and delete calls using + // the same trick in ToJsObject and ToJsArrayBuffer. HostFunctionProxy *hostFuncProxy = new HostFunctionProxy(func, *this); JsValueRef funcRef; - checkException(JsCreateFunction( + VerifyJsErrorElseThrow(JsCreateFunction( ChakraRuntime::HostFunctionCall, hostFuncProxy, &funcRef)); - checkException(JsSetObjectBeforeCollectCallback( + + VerifyJsErrorElseThrow(JsSetObjectBeforeCollectCallback( funcRef, hostFuncProxy, [](JsRef ref, void *hostFuncProxy) { delete hostFuncProxy; })); - return createObject(funcRef).getFunction(*this); + return MakePointer(funcRef).getFunction(*this); } facebook::jsi::Value ChakraRuntime::call( - const facebook::jsi::Function &f, + const facebook::jsi::Function &func, const facebook::jsi::Value &jsThis, const facebook::jsi::Value *args, size_t count) { - JsValueRef result = nullptr; - checkException(JsCallFunction( - objectRef(f), - ArgsConverterForCall( - *this, - jsThis.isUndefined() ? nullptr : objectRef(jsThis.getObject(*this)), - args, - count), - static_cast(count + 1), + // We must store these ChakraObjectRefs on the stack to make sure that they do + // not go out of scope when JsCallFunction is called. + ChakraObjectRef thisRef = ToChakraObjectRef(jsThis); + std::vector argRefs = ToChakraObjectRefs(args, count); + + std::vector argsWithThis = + constructJsFunctionArguments(thisRef, argRefs); + assert(argsWithThis.size() <= USHRT_MAX); + + JsValueRef result; + VerifyJsErrorElseThrow(JsCallFunction( + GetChakraObjectRef(func), + argsWithThis.data(), + static_cast(argsWithThis.size()), &result)); - return createValue(result); + return ToJsiValue(ChakraObjectRef(result)); } facebook::jsi::Value ChakraRuntime::callAsConstructor( - const facebook::jsi::Function &f, + const facebook::jsi::Function &func, const facebook::jsi::Value *args, size_t count) { - JsValueRef result = nullptr; - checkException(JsConstructObject( - objectRef(f), - ArgsConverterForCall(*this, nullptr, args, count), - static_cast(count), + // We must store these ChakraObjectRefs on the stack to make sure that they do + // not go out of scope when JsConstructObject is called. + ChakraObjectRef undefinedRef = ToChakraObjectRef(facebook::jsi::Value()); + std::vector argRefs = ToChakraObjectRefs(args, count); + + std::vector argsWithThis = + constructJsFunctionArguments(undefinedRef, argRefs); + assert(argsWithThis.size() <= USHRT_MAX); + + JsValueRef result; + VerifyJsErrorElseThrow(JsConstructObject( + GetChakraObjectRef(func), + argsWithThis.data(), + static_cast(argsWithThis.size()), &result)); - return createValue(result); + return ToJsiValue(ChakraObjectRef(result)); } facebook::jsi::Runtime::ScopeState *ChakraRuntime::pushScope() { @@ -607,284 +593,196 @@ facebook::jsi::Runtime::ScopeState *ChakraRuntime::pushScope() { void ChakraRuntime::popScope(Runtime::ScopeState *state) { assert(state == nullptr); - checkException(JsCollectGarbage(m_runtime), "JsCollectGarbage"); + VerifyJsErrorElseThrow(JsCollectGarbage(m_runtime)); } bool ChakraRuntime::strictEquals( const facebook::jsi::String &a, const facebook::jsi::String &b) const { - bool result; - JsStrictEquals(stringRef(a), stringRef(b), &result); - return result; + return CompareJsValues(GetChakraObjectRef(a), GetChakraObjectRef(b)); } bool ChakraRuntime::strictEquals( const facebook::jsi::Object &a, const facebook::jsi::Object &b) const { - bool result; - JsStrictEquals(objectRef(a), objectRef(b), &result); - return result; + return CompareJsValues(GetChakraObjectRef(a), GetChakraObjectRef(b)); } bool ChakraRuntime::strictEquals( - const facebook::jsi::Symbol &, - const facebook::jsi::Symbol &) const { - throw facebook::jsi::JSINativeException("Not implemented!"); + const facebook::jsi::Symbol &a, + const facebook::jsi::Symbol &b) const { + return CompareJsValues(GetChakraObjectRef(a), GetChakraObjectRef(b)); } bool ChakraRuntime::instanceOf( - const facebook::jsi::Object &o, - const facebook::jsi::Function &f) { - bool res; - checkException(JsInstanceOf(objectRef(o), objectRef(f), &res)); - return res; + const facebook::jsi::Object &obj, + const facebook::jsi::Function &func) { + bool result; + VerifyJsErrorElseThrow( + JsInstanceOf(GetChakraObjectRef(obj), GetChakraObjectRef(func), &result)); + return result; } #pragma endregion Functions_inherited_from_Runtime -facebook::jsi::Value ChakraRuntime::createValue(JsValueRef value) const { - JsValueType type; - JsGetValueType(value, &type); +facebook::jsi::Value ChakraRuntime::ToJsiValue(ChakraObjectRef &&ref) { + JsValueType type = GetValueType(ref); switch (type) { - case JsUndefined: + case JsUndefined: { return facebook::jsi::Value(); - - case JsNull: + break; + } + case JsNull: { return facebook::jsi::Value(nullptr); - + break; + } case JsNumber: { - double doubleValue; - JsNumberToDouble(value, &doubleValue); - facebook::jsi::Value val(doubleValue); - return val; + double number; + VerifyJsErrorElseThrow(JsNumberToDouble(ref, &number)); + return facebook::jsi::Value(number); + break; } - case JsString: { - std::string utf8str = JSStringToSTLString(value); - return facebook::jsi::String::createFromUtf8( - *const_cast( - reinterpret_cast(this)), - reinterpret_cast(utf8str.c_str()), - utf8str.size()); + return facebook::jsi::Value( + *this, MakePointer(std::move(ref))); break; } - case JsBoolean: { - bool boolValue; - JsBooleanToBool(value, &boolValue); - facebook::jsi::Value val(boolValue); - return val; + bool b; + VerifyJsErrorElseThrow(JsBooleanToBool(ref, &b)); + return facebook::jsi::Value(b); + break; } - - case JsObject: - case JsFunction: - case JsArray: { - return facebook::jsi::Value(createObject(value)); + case JsSymbol: { + return facebook::jsi::Value( + *this, MakePointer(std::move(ref))); break; } - + case JsObject: + case JsFunction: case JsError: - case JsSymbol: + case JsArray: case JsArrayBuffer: case JsTypedArray: - case JsDataView: - default: + case JsDataView: { + return facebook::jsi::Value( + *this, MakePointer(std::move(ref))); + break; + } + default: { + // Control flow should never reach here. std::terminate(); break; + } } -} - -JsValueRef ChakraRuntime::valueRef(const facebook::jsi::Value &valueIn) { - if (valueIn.isUndefined()) { - JsValueRef value; - JsGetUndefinedValue(&value); - return value; - } else if (valueIn.isNull()) { - JsValueRef value; - JsGetNullValue(&value); - return value; - } else if (valueIn.isBool()) { - JsValueRef value; - JsBoolToBoolean(valueIn.getBool(), &value); - return value; - } else if (valueIn.isNumber()) { - JsValueRef value; - JsDoubleToNumber(valueIn.getNumber(), &value); - return value; - } else if (valueIn.isString()) { - return stringRef(valueIn.getString(*this)); - } else if (valueIn.isObject()) { - return objectRef(valueIn.getObject(*this)); - } else { - // What are you? - abort(); - } -} -ChakraRuntime::ChakraPropertyIdValue::~ChakraPropertyIdValue() { - JsRelease(m_propId, nullptr); + // Control flow should never reach here. + std::terminate(); } -void ChakraRuntime::ChakraPropertyIdValue::invalidate() { - delete this; -} +ChakraObjectRef ChakraRuntime::ToChakraObjectRef( + const facebook::jsi::Value &value) { + if (value.isUndefined()) { + JsValueRef ref; + VerifyJsErrorElseThrow(JsGetUndefinedValue(&ref)); + return ChakraObjectRef(ref); -ChakraRuntime::ChakraPropertyIdValue::ChakraPropertyIdValue( - JsPropertyIdRef propIdRef) - : m_propId(propIdRef) { - JsAddRef(propIdRef, nullptr); -} + } else if (value.isNull()) { + JsValueRef ref; + VerifyJsErrorElseThrow(JsGetNullValue(&ref)); + return ChakraObjectRef(ref); -ChakraRuntime::ChakraStringValue::ChakraStringValue(JsValueRef str) - : m_str(str) { - JsAddRef(str, nullptr); -} + } else if (value.isBool()) { + JsValueRef ref; + VerifyJsErrorElseThrow(JsBoolToBoolean(value.getBool(), &ref)); + return ChakraObjectRef(ref); -void ChakraRuntime::ChakraStringValue::invalidate() { - delete this; -} + } else if (value.isNumber()) { + JsValueRef ref; + VerifyJsErrorElseThrow(JsDoubleToNumber(value.asNumber(), &ref)); + return ChakraObjectRef(ref); -ChakraRuntime::ChakraStringValue::~ChakraStringValue() { - JsRelease(m_str, nullptr); -} + } else if (value.isSymbol()) { + return GetChakraObjectRef(value.asSymbol(*this)); -ChakraRuntime::ChakraObjectValue::ChakraObjectValue(JsValueRef obj) - : m_obj(obj) { - JsAddRef(m_obj, nullptr); -} + } else if (value.isString()) { + return GetChakraObjectRef(value.asString(*this)); -void ChakraRuntime::ChakraObjectValue::invalidate() { - delete this; -} + } else if (value.isObject()) { + return GetChakraObjectRef(value.asObject(*this)); -ChakraRuntime::ChakraObjectValue::~ChakraObjectValue() { - JsRelease(m_obj, nullptr); + } else { + // Control flow should never reach here. + std::terminate(); + } } -ChakraRuntime::ChakraWeakRefValue::ChakraWeakRefValue(JsWeakRef obj) - : m_obj(obj) { - JsAddRef(m_obj, nullptr); -} +std::vector ChakraRuntime::ToChakraObjectRefs( + const facebook::jsi::Value *value, + size_t count) { + std::vector result{}; -void ChakraRuntime::ChakraWeakRefValue::invalidate() { - delete this; -} + for (unsigned int i = 0; i < count; ++i) { + result.emplace_back(ToChakraObjectRef(*value)); + ++value; + } -ChakraRuntime::ChakraWeakRefValue::~ChakraWeakRefValue() { - JsRelease(m_obj, nullptr); + return result; } -template -/*static */ facebook::jsi::Object ChakraRuntime::ObjectWithExternalData< - T>::create(ChakraRuntime &rt, T *externalData) { - return rt.createObject(static_cast(nullptr), externalData); +// clang-format off +template +/* static */ facebook::jsi::Object +ChakraRuntime::ObjectWithExternalData::create( + ChakraRuntime &runtime, + std::unique_ptr &&externalData) { + return runtime.MakePointer>( + ToJsObject(std::move(externalData))); } -template -/*static */ ChakraRuntime::ObjectWithExternalData +template +/* static */ ChakraRuntime::ObjectWithExternalData ChakraRuntime::ObjectWithExternalData::fromExisting( - ChakraRuntime &rt, + ChakraRuntime &runtime, facebook::jsi::Object &&obj) { - return ObjectWithExternalData(rt.cloneObject(getPointerValue(obj))); + return ObjectWithExternalData(runtime.cloneObject(getPointerValue(obj))); } +// clang-format on -template +template T *ChakraRuntime::ObjectWithExternalData::getExternalData() { T *externalData; - JsGetExternalData( - static_cast(getPointerValue(*this))->m_obj, - reinterpret_cast(&externalData)); + VerifyChakraErrorElseThrow(JsGetExternalData( + GetChakraObjectRef(*this), reinterpret_cast(&externalData))); return externalData; } -void ChakraRuntime::checkException(JsErrorCode result) { - bool hasException = false; - if (result == JsNoError && (JsHasException(&hasException), !hasException)) - return; - - checkException(result, nullptr); -} - -void ChakraRuntime::checkException(JsErrorCode result, const char *message) { - bool hasException = false; - if (result == JsNoError && (JsHasException(&hasException), !hasException)) - return; - - std::ostringstream errorStream; - - if (message != nullptr) - errorStream << message << ". "; - - if (result != JsNoError) { - errorStream << "ChakraCore API Error :" << std::hex << result << ". "; - } - - if (hasException || result == JsErrorScriptException) { - errorStream << "JS exception found: "; - - JsValueRef exn; - JsGetAndClearException(&exn); - - JsPropertyIdRef messageName; - JsGetPropertyIdFromName(L"stack", &messageName); - - JsValueRef messageValue; - if (JsGetProperty(exn, messageName, &messageValue) == JsNoError) { - if (JsConvertValueToString(exn, &messageValue) != JsNoError) { - errorStream << "Unable to retrieve JS exception stack"; - } else { - errorStream << JSStringToSTLString(messageValue); - } - } else { - JsValueRef exnJStr; - if (JsConvertValueToString(exn, &exnJStr) != JsNoError) { - errorStream << "Unable to describe JS exception"; - } else { - errorStream << JSStringToSTLString(exnJStr); - } +void ChakraRuntime::VerifyJsErrorElseThrow(JsErrorCode error) { + switch (error) { + case JsNoError: { + return; + break; } - } - - std::string errorString = errorStream.str(); - throw facebook::jsi::JSError( - *this, createStringFromAscii(errorString.c_str(), errorString.length())); -} - -JsValueRef ChakraRuntime::stringRef(const facebook::jsi::String &str) { - return static_cast(getPointerValue(str))->m_str; -} - -JsPropertyIdRef ChakraRuntime::propIdRef(const facebook::jsi::PropNameID &sym) { - return static_cast(getPointerValue(sym)) - ->m_propId; -} - -JsValueRef ChakraRuntime::objectRef(const facebook::jsi::Object &obj) { - return static_cast(getPointerValue(obj))->m_obj; -} - -JsWeakRef ChakraRuntime::objectRef(const facebook::jsi::WeakObject &obj) { - return static_cast(getPointerValue(obj))->m_obj; -} -facebook::jsi::String ChakraRuntime::createString(JsValueRef str) const { - return make(makeStringValue(str)); -} - -facebook::jsi::PropNameID ChakraRuntime::createPropNameID(JsValueRef str) { - return make(makePropertyIdValue(str)); -} + case JsErrorScriptException: { + JsValueRef jsError; + VerifyChakraErrorElseThrow(JsGetAndClearException(&jsError)); + throw facebook::jsi::JSError( + "A JavaScript Error was thrown.", + *this, + ToJsiValue(ChakraObjectRef(jsError))); + break; + } -template -facebook::jsi::Object ChakraRuntime::createObject( - JsValueRef objectRef, - T *externalData) const { - return make(makeObjectValue(objectRef, externalData)); -} + default: { + VerifyChakraErrorElseThrow(error); + break; + } + } // switch (error) -facebook::jsi::Object ChakraRuntime::createObject(JsValueRef obj) const { - return make(makeObjectValue(obj)); + // Control flow should never reach here. + std::terminate(); } facebook::jsi::Object ChakraRuntime::createProxy( @@ -1011,89 +909,6 @@ facebook::jsi::Object ChakraRuntime::createHostObjectProxyHandler() noexcept { return handlerObj; } -facebook::jsi::Runtime::PointerValue *ChakraRuntime::makeStringValue( - JsValueRef stringRef) const { - if (!stringRef) { - JsValueRef emptyJsValue = createJSString("", 0); - stringRef = emptyJsValue; - } - return new ChakraStringValue(stringRef); -} - -facebook::jsi::Runtime::PointerValue *ChakraRuntime::makeObjectValue( - JsValueRef objectRef) const { - if (!objectRef) { - JsCreateObject(&objectRef); - } - - ChakraObjectValue *chakraObjValue = new ChakraObjectValue(objectRef); - return chakraObjValue; -} - -template -facebook::jsi::Runtime::PointerValue *ChakraRuntime::makeObjectValue( - JsValueRef objectRef, - T *externaldata) const { - if (!externaldata) { - return makeObjectValue(objectRef); - } - - // Note :: We explicitly delete the external data proxy when the JS value is - // finalized. The proxy is expected to do the right thing in destructor, for - // e.g. decrease the ref count of a shared resource. - if (!objectRef) { - JsCreateExternalObject( - externaldata, [](void *data) { delete data; }, &objectRef); - } else { - JsSetExternalData( - objectRef, externaldata); // TODO : Is there an API to listen to - // finalization of arbitrary objects ? - } - - ChakraObjectValue *chakraObjValue = new ChakraObjectValue(objectRef); - return chakraObjValue; -} - -facebook::jsi::Runtime::PointerValue *ChakraRuntime::makePropertyIdValue( - JsPropertyIdRef propIdRef) const { - if (!propIdRef) { - std::terminate(); - } - return new ChakraPropertyIdValue(propIdRef); -} - -facebook::jsi::Runtime::PointerValue *ChakraRuntime::makeWeakRefValue( - JsWeakRef objWeakRef) const { - if (!objWeakRef) - std::terminate(); - return new ChakraWeakRefValue(objWeakRef); -} - -std::wstring ChakraRuntime::JSStringToSTLWString(JsValueRef str) { - const wchar_t *value; - size_t length; - - if (JsNoError != JsStringToPointer(str, &value, &length)) { - std::terminate(); - } - - // Note: Copying the string out of JsString, as required. - return std::wstring(value, length); -} - -std::string ChakraRuntime::JSStringToSTLString(JsValueRef str) { - const wchar_t *value; - size_t length; - - if (JsNoError != JsStringToPointer(str, &value, &length)) { - std::terminate(); - } - - // Note: This results in multiple buffer copyings. We should look for - // optimization. - return Microsoft::Common::Unicode::Utf16ToUtf8(std::wstring(value, length)); -} - void ChakraRuntime::setupMemoryTracker() noexcept { if (runtimeArgs().memoryTracker) { size_t initialMemoryUsage = 0; @@ -1109,7 +924,8 @@ void ChakraRuntime::setupMemoryTracker() noexcept { [](void *callbackState, JsMemoryEventType allocationEvent, size_t allocationSize) -> bool { - auto memoryTrackerPtr = static_cast(callbackState); + auto memoryTrackerPtr = + static_cast(callbackState); switch (allocationEvent) { case JsMemoryAllocate: memoryTrackerPtr->OnAllocation(allocationSize); @@ -1148,36 +964,45 @@ JsValueRef CALLBACK ChakraRuntime::HostFunctionCall( if (argumentCount > maxStackArgCount) { heapArgs = std::make_unique(argumentCount); + for (size_t i = 1; i < argumentCountIncThis; i++) { - heapArgs[i - 1] = - hostFuncProxy.getRuntime().createValue(argumentsIncThis[i]); + heapArgs[i - 1] = hostFuncProxy.getRuntime().ToJsiValue( + ChakraObjectRef(argumentsIncThis[i])); } args = heapArgs.get(); + } else { for (size_t i = 1; i < argumentCountIncThis; i++) { - stackArgs[i - 1] = - hostFuncProxy.getRuntime().createValue(argumentsIncThis[i]); + stackArgs[i - 1] = hostFuncProxy.getRuntime().ToJsiValue( + ChakraObjectRef(argumentsIncThis[i])); } args = stackArgs; } + JsValueRef res{JS_INVALID_REFERENCE}; facebook::jsi::Value thisVal( - hostFuncProxy.getRuntime().createObject(argumentsIncThis[0])); + hostFuncProxy.getRuntime().MakePointer( + argumentsIncThis[0])); + try { facebook::jsi::Value retVal = hostFuncProxy.getHostFunction()( hostFuncProxy.getRuntime(), thisVal, args, argumentCount); - res = hostFuncProxy.getRuntime().valueRef(retVal); + res = hostFuncProxy.getRuntime().ToChakraObjectRef(retVal); + } catch (const facebook::jsi::JSError &error) { - JsSetException(hostFuncProxy.getRuntime().valueRef(error.value())); + JsSetException(hostFuncProxy.getRuntime().ToChakraObjectRef(error.value())); + } catch (const std::exception &ex) { std::string exwhat(ex.what()); JsValueRef exn; - exn = createJSString(exwhat.c_str(), exwhat.size()); + exn = ToJsString(std::string_view{exwhat.c_str(), exwhat.size()}); JsSetException(exn); + } catch (...) { std::string exceptionString("Exception in HostFunction: "); JsValueRef exn; - exn = createJSString(exceptionString.c_str(), exceptionString.size()); + exn = ToJsString( + std::string_view{exceptionString.c_str(), exceptionString.size()}); JsSetException(exn); } diff --git a/vnext/JSI/Shared/ChakraRuntime.h b/vnext/JSI/Shared/ChakraRuntime.h index 1219b0f0422..33d7a471b97 100644 --- a/vnext/JSI/Shared/ChakraRuntime.h +++ b/vnext/JSI/Shared/ChakraRuntime.h @@ -3,7 +3,7 @@ #pragma once -#include "ByteArrayBuffer.h" +#include "ChakraObjectRef.h" #include "ChakraRuntimeArgs.h" #include "jsi/jsi.h" @@ -25,7 +25,6 @@ #if !defined(CHAKRACORE) class DebugProtocolHandler {}; class DebugService {}; -using JsWeakRef = JsValueRef; #endif namespace Microsoft::JSI { @@ -59,10 +58,17 @@ class ChakraRuntime : public facebook::jsi::Runtime { // Instrumentation instance which returns no metrics. private: - PointerValue *cloneSymbol(const PointerValue *pv) override; - PointerValue *cloneString(const PointerValue *pv) override; - PointerValue *cloneObject(const PointerValue *pv) override; - PointerValue *clonePropNameID(const PointerValue *pv) override; + // Despite the name "clone" suggesting a deep copy, a return value of these + // functions points to a new heap allocated ChakraPointerValue whose memeber + // ChakraObjectRef refers to the same JavaScript object as the member + // ChakraObjectRef of *pointerValue. This behavior is consistent with that of + // HermesRuntime and JSCRuntime. Also, Like all ChakraPointerValues, the + // return value must only be used as an argument to the constructor of + // jsi::Pointer or one of its derived classes. + PointerValue *cloneSymbol(const PointerValue *pointerValue) override; + PointerValue *cloneString(const PointerValue *pointerValue) override; + PointerValue *cloneObject(const PointerValue *pointerValue) override; + PointerValue *clonePropNameID(const PointerValue *pointerValue) override; facebook::jsi::PropNameID createPropNameIDFromAscii( const char *str, @@ -72,13 +78,15 @@ class ChakraRuntime : public facebook::jsi::Runtime { size_t length) override; facebook::jsi::PropNameID createPropNameIDFromString( const facebook::jsi::String &str) override; - std::string utf8(const facebook::jsi::PropNameID &str) override; + std::string utf8(const facebook::jsi::PropNameID &id) override; bool compare( const facebook::jsi::PropNameID &lhs, const facebook::jsi::PropNameID &rhs) override; std::string symbolToString(const facebook::jsi::Symbol &s) override; + // Despite its name, createPropNameIDFromAscii is the same function as + // createStringFromUtf8. facebook::jsi::String createStringFromAscii(const char *str, size_t length) override; facebook::jsi::String createStringFromUtf8(const uint8_t *utf8, size_t length) @@ -119,6 +127,8 @@ class ChakraRuntime : public facebook::jsi::Runtime { bool isFunction(const facebook::jsi::Object &obj) const override; bool isHostObject(const facebook::jsi::Object &obj) const override; bool isHostFunction(const facebook::jsi::Function &func) const override; + // Returns the names of all enumerable properties of an object. This + // corresponds the properties iterated through by the JavaScript for..in loop. facebook::jsi::Array getPropertyNames( const facebook::jsi::Object &obj) override; @@ -130,6 +140,9 @@ class ChakraRuntime : public facebook::jsi::Runtime { facebook::jsi::Array createArray(size_t length) override; size_t size(const facebook::jsi::Array &arr) override; size_t size(const facebook::jsi::ArrayBuffer &arrBuf) override; + // The lifetime of the buffer returned is the same as the lifetime of the + // ArrayBuffer. The returned buffer pointer does not count as a reference to + // the ArrayBuffer for the purpose of garbage collection. uint8_t *data(const facebook::jsi::ArrayBuffer &arrBuf) override; facebook::jsi::Value getValueAtIndex( const facebook::jsi::Array &arr, @@ -169,17 +182,18 @@ class ChakraRuntime : public facebook::jsi::Runtime { const facebook::jsi::Object &b) const override; bool instanceOf( - const facebook::jsi::Object &o, - const facebook::jsi::Function &f) override; + const facebook::jsi::Object &obj, + const facebook::jsi::Function &func) override; #pragma endregion Functions_inherited_from_Runtime public: - // JsValueRef->JSValue (needs make.*Value so it must be member function) - facebook::jsi::Value createValue(JsValueRef value) const; - - // Value->JsValueRef (similar to above) - JsValueRef valueRef(const facebook::jsi::Value &value); + // These three functions only performs shallow copies. + facebook::jsi::Value ToJsiValue(ChakraObjectRef &&ref); + ChakraObjectRef ToChakraObjectRef(const facebook::jsi::Value &value); + std::vector ToChakraObjectRefs( + const facebook::jsi::Value *value, + size_t count); protected: ChakraRuntimeArgs &runtimeArgs() { @@ -187,86 +201,130 @@ class ChakraRuntime : public facebook::jsi::Runtime { } private: - class ChakraPropertyIdValue final : public PointerValue { - ChakraPropertyIdValue(JsPropertyIdRef str); - ~ChakraPropertyIdValue(); - - void invalidate() override; - - JsPropertyIdRef m_propId; + // ChakraPointerValue is needed for working with Facebook's jsi::Pointer class + // and must only be used for this purpose. Every instance of + // ChakraPointerValue should be allocated on the heap and be used as an + // argument to the constructor of jsi::Pointer or one of its derived classes. + // Pointer makes sure that invalidate(), which frees the heap allocated + // ChakraPointerValue, is called upon destruction. Since the constructor of + // jsi::Pointer is protected, we usually have to invoke it through + // jsi::Runtime::make. The code should look something like: + // + // make(new ChakraPointerValue(...)); + // + // or you can use the helper function MakePointer(), as defined below. + template + struct ChakraPointerValueTemplate : PointerValue { + public: + ChakraPointerValueTemplate(const T &ref) noexcept : m_ref{ref} { + static_assert( + std::is_same::value || + // Since only ChakraCore offers the JsWeakRef type alias, we + // cannot use it here; so void* is the best alternative we can use + // here. + std::is_same::value, + "ChakraPointerValueTemplate should only be instantiated for " + "ChakraObjectRef and JsWeakRef."); + } - protected: - friend class ChakraRuntime; - }; + ChakraPointerValueTemplate(T &&ref) noexcept : m_ref{std::move(ref)} {} + + // Declaring ~ChakraPointerValueTemplate() private prevents the compiler + // from implicitly generating the following functions, so we have to tell + // the compiler to do so. + ChakraPointerValueTemplate( + const ChakraPointerValueTemplate &other) noexcept = default; + ChakraPointerValueTemplate(ChakraPointerValueTemplate &&other) noexcept = + default; + ChakraPointerValueTemplate &operator=( + const ChakraPointerValueTemplate &rhs) noexcept = default; + ChakraPointerValueTemplate &operator=( + ChakraPointerValueTemplate &&rhs) noexcept = default; + + inline void invalidate() noexcept override { + delete this; + } - class ChakraStringValue final : public PointerValue { - ChakraStringValue(JsValueRef str); - ~ChakraStringValue(); + inline const T &GetRef() const noexcept { + return m_ref; + } - void invalidate() override; + private: + // ~ChakraPointerValueTemplate() should only be invoked by invalidate(). + // Hence we make it private. + ~ChakraPointerValueTemplate() noexcept = default; - protected: - friend class ChakraRuntime; - JsValueRef m_str; + T m_ref; }; - class ChakraObjectValue final : public PointerValue { - ChakraObjectValue(JsValueRef obj); - ~ChakraObjectValue(); + using ChakraPointerValue = ChakraPointerValueTemplate; - void invalidate() override; - - protected: - friend class ChakraRuntime; - JsValueRef m_obj; - }; + template + inline T MakePointer(JsValueRef ref) { + return MakePointer(ChakraObjectRef(ref)); + } - class ChakraWeakRefValue final : public PointerValue { - ChakraWeakRefValue(JsWeakRef obj); - ~ChakraWeakRefValue(); + template + inline T MakePointer(ChakraObjectRef &&ref) { + static_assert( + std::is_base_of::value, + "MakePointer should only be instantiated for classes derived from " + "facebook::jsi::Pointer."); + return make(new ChakraPointerValue(std::move(ref))); + } - void invalidate() override; + // The pointer passed to this function must point to a ChakraPointerValue. + inline static ChakraPointerValue *CloneChakraPointerValue( + const PointerValue *pointerValue) { + return new ChakraPointerValue( + *(static_cast(pointerValue))); + } - protected: - friend class ChakraRuntime; - JsWeakRef m_obj; - }; + // The jsi::Pointer passed to this function must hold a ChakraPointerValue. + inline static const ChakraObjectRef &GetChakraObjectRef( + const facebook::jsi::Pointer &p) { + return static_cast(getPointerValue(p)) + ->GetRef(); + } class HostObjectProxy { public: facebook::jsi::Value Get(const facebook::jsi::PropNameID &propNameId) { - return hostObject_->get(runtime_, propNameId); + return m_hostObject->get(m_runtime, propNameId); } void Set( const facebook::jsi::PropNameID &propNameId, const facebook::jsi::Value &value) { - hostObject_->set(runtime_, propNameId, value); + m_hostObject->set(m_runtime, propNameId, value); } std::vector Enumerator() { - return hostObject_->getPropertyNames(runtime_); + return m_hostObject->getPropertyNames(m_runtime); } HostObjectProxy( ChakraRuntime &rt, const std::shared_ptr &hostObject) - : runtime_(rt), hostObject_(hostObject) {} + : m_runtime(rt), m_hostObject(hostObject) {} std::shared_ptr getHostObject() { - return hostObject_; + return m_hostObject; } private: - ChakraRuntime &runtime_; - std::shared_ptr hostObject_; + ChakraRuntime &m_runtime; + std::shared_ptr m_hostObject; }; - template + template class ObjectWithExternalData : public facebook::jsi::Object { public: - static facebook::jsi::Object create(ChakraRuntime &rt, T *externalData); + static facebook::jsi::Object create( + ChakraRuntime &runtime, + std::unique_ptr &&externalData); + static ObjectWithExternalData fromExisting( - ChakraRuntime &rt, + ChakraRuntime &runtime, facebook::jsi::Object &&obj); public: @@ -275,33 +333,14 @@ class ChakraRuntime : public facebook::jsi::Runtime { : Object(const_cast(value)) { } // TODO :: const_cast - ObjectWithExternalData(ObjectWithExternalData &&) = default; - ObjectWithExternalData &operator=(ObjectWithExternalData &&) = default; + ObjectWithExternalData(ObjectWithExternalData &&other) = default; + ObjectWithExternalData &operator=(ObjectWithExternalData &&other) = default; }; template friend class ObjectWithExternalData; - inline void checkException(JsErrorCode res); - inline void checkException(JsErrorCode res, const char *msg); - - // Basically convenience casts - static JsValueRef stringRef(const facebook::jsi::String &str); - static JsPropertyIdRef propIdRef(const facebook::jsi::PropNameID &sym); - static JsValueRef objectRef(const facebook::jsi::Object &obj); - static JsWeakRef objectRef(const facebook::jsi::WeakObject &obj); - - static JsWeakRef newWeakObjectRef(const facebook::jsi::Object &obj); - static JsValueRef strongObjectRef(const facebook::jsi::WeakObject &obj); - - // Factory methods for creating String/Object - facebook::jsi::String createString(JsValueRef stringRef) const; - facebook::jsi::PropNameID createPropNameID(JsValueRef stringRef); - - template - facebook::jsi::Object createObject(JsValueRef objectRef, T *externalData) - const; - facebook::jsi::Object createObject(JsValueRef objectRef) const; + void VerifyJsErrorElseThrow(JsErrorCode error); facebook::jsi::Object createProxy( facebook::jsi::Object &&target, @@ -309,27 +348,6 @@ class ChakraRuntime : public facebook::jsi::Runtime { facebook::jsi::Function createProxyConstructor() noexcept; facebook::jsi::Object createHostObjectProxyHandler() noexcept; - // Used by factory methods and clone methods - facebook::jsi::Runtime::PointerValue *makeStringValue(JsValueRef str) const; - - template - facebook::jsi::Runtime::PointerValue *makeObjectValue( - JsValueRef obj, - T *externaldata) const; - facebook::jsi::Runtime::PointerValue *makeObjectValue(JsValueRef obj) const; - - facebook::jsi::Runtime::PointerValue *makePropertyIdValue( - JsPropertyIdRef propId) const; - - facebook::jsi::Runtime::PointerValue *makeWeakRefValue(JsWeakRef obj) const; - - // String helpers - static std::wstring JSStringToSTLWString(JsValueRef str); - static std::string JSStringToSTLString(JsValueRef str); - - static JsValueRef createJSString(const char *data, size_t length); - static JsValueRef createJSPropertyId(const char *data, size_t length); - // Promise Helpers static void CALLBACK PromiseContinuationCallback(JsValueRef funcRef, void *callbackState) noexcept; @@ -394,7 +412,7 @@ class ChakraRuntime : public facebook::jsi::Runtime { ChakraRuntimeArgs m_args; JsRuntimeHandle m_runtime; - JsContextRef m_ctx; + ChakraObjectRef m_context; // Note: For simplicity, We are pinning the script and serialized script // buffers in the facebook::jsi::Runtime instance assuming as these buffers diff --git a/vnext/JSI/Shared/JSI.Shared.vcxitems b/vnext/JSI/Shared/JSI.Shared.vcxitems index 8b4e9d778f4..f2e4ac6186f 100644 --- a/vnext/JSI/Shared/JSI.Shared.vcxitems +++ b/vnext/JSI/Shared/JSI.Shared.vcxitems @@ -14,10 +14,12 @@ + + diff --git a/vnext/JSI/Shared/JSI.Shared.vcxitems.filters b/vnext/JSI/Shared/JSI.Shared.vcxitems.filters index 9023c2bc4cb..ce60891f4a3 100644 --- a/vnext/JSI/Shared/JSI.Shared.vcxitems.filters +++ b/vnext/JSI/Shared/JSI.Shared.vcxitems.filters @@ -21,10 +21,16 @@ Header Files + + Header Files + Source Files + + Source Files + \ No newline at end of file diff --git a/vnext/JSI/Universal/ChakraJsiRuntime_edgemode.cpp b/vnext/JSI/Universal/ChakraJsiRuntime_edgemode.cpp index 2251a9b6b5f..54fb73e6e5c 100644 --- a/vnext/JSI/Universal/ChakraJsiRuntime_edgemode.cpp +++ b/vnext/JSI/Universal/ChakraJsiRuntime_edgemode.cpp @@ -2,6 +2,8 @@ // Licensed under the MIT License. #include "ChakraRuntime.h" + +#include "ByteArrayBuffer.h" #include "Unicode.h" #if !defined(CHAKRACORE) @@ -9,32 +11,6 @@ namespace Microsoft::JSI { -JsWeakRef ChakraRuntime::newWeakObjectRef(const facebook::jsi::Object &obj) { - return objectRef(obj); -} - -JsValueRef ChakraRuntime::strongObjectRef( - const facebook::jsi::WeakObject &obj) { - return objectRef(obj); // Return the original strong ref. -} - -JsValueRef ChakraRuntime::createJSString(const char *data, size_t length) { - const std::wstring script16 = Microsoft::Common::Unicode::Utf8ToUtf16( - reinterpret_cast(data), length); - JsValueRef value; - JsPointerToString(script16.c_str(), script16.size(), &value); - return value; -} - -JsValueRef ChakraRuntime::createJSPropertyId(const char *data, size_t length) { - JsValueRef propIdRef; - const std::wstring name16 = Microsoft::Common::Unicode::Utf8ToUtf16( - reinterpret_cast(data), length); - if (JsNoError != JsGetPropertyIdFromName(name16.c_str(), &propIdRef)) - std::terminate(); - return propIdRef; -} - void ChakraRuntime::setupNativePromiseContinuation() noexcept { // NOP } @@ -83,17 +59,15 @@ facebook::jsi::Value ChakraRuntime::evaluateJavaScriptSimple( throw facebook::jsi::JSINativeException("Script can't be empty."); const std::wstring url16 = Microsoft::Common::Unicode::Utf8ToUtf16(sourceURL); - if (url16.empty()) - throw facebook::jsi::JSINativeException("Script URL can't be empty."); JsValueRef result; - checkException(JsRunScript( + VerifyJsErrorElseThrow(JsRunScript( script16.c_str(), JS_SOURCE_CONTEXT_NONE /*sourceContext*/, url16.c_str(), &result)); - return createValue(result); + return ToJsiValue(ChakraObjectRef(result)); } // TODO :: Return result @@ -119,7 +93,7 @@ bool ChakraRuntime::evaluateSerializedScript( } else if (ret == JsErrorBadSerializedScript) { return false; } else { - checkException(ret); + VerifyChakraErrorElseThrow(ret); return true; } }