diff --git a/bin/ChakraCore/ChakraCore.def b/bin/ChakraCore/ChakraCore.def index 3010e888d9b..33062845b2a 100644 --- a/bin/ChakraCore/ChakraCore.def +++ b/bin/ChakraCore/ChakraCore.def @@ -64,3 +64,5 @@ JsLessThanOrEqual JsCreateEnhancedFunction JsSetHostPromiseRejectionTracker + +JsGetProxyProperties diff --git a/bin/ch/ChakraRtInterface.cpp b/bin/ch/ChakraRtInterface.cpp index 503d6b8c025..ee6133310d2 100644 --- a/bin/ch/ChakraRtInterface.cpp +++ b/bin/ch/ChakraRtInterface.cpp @@ -154,6 +154,7 @@ bool ChakraRTInterface::LoadChakraDll(ArgInfo* argInfo, HINSTANCE *outLibrary) m_jsApiHooks.pfJsrtCopyString = (JsAPIHooks::JsrtCopyString)GetChakraCoreSymbol(library, "JsCopyString"); m_jsApiHooks.pfJsrtCreatePropertyId = (JsAPIHooks::JsrtCreatePropertyId)GetChakraCoreSymbol(library, "JsCreatePropertyId"); m_jsApiHooks.pfJsrtCreateExternalArrayBuffer = (JsAPIHooks::JsrtCreateExternalArrayBuffer)GetChakraCoreSymbol(library, "JsCreateExternalArrayBuffer"); + m_jsApiHooks.pfJsrtGetProxyProperties = (JsAPIHooks::JsrtGetProxyProperties)GetChakraCoreSymbol(library, "JsGetProxyProperties"); m_jsApiHooks.pfJsrtTTDCreateRecordRuntime = (JsAPIHooks::JsrtTTDCreateRecordRuntimePtr)GetChakraCoreSymbol(library, "JsTTDCreateRecordRuntime"); m_jsApiHooks.pfJsrtTTDCreateReplayRuntime = (JsAPIHooks::JsrtTTDCreateReplayRuntimePtr)GetChakraCoreSymbol(library, "JsTTDCreateReplayRuntime"); diff --git a/bin/ch/ChakraRtInterface.h b/bin/ch/ChakraRtInterface.h index 5bd00024930..f530c5edb40 100644 --- a/bin/ch/ChakraRtInterface.h +++ b/bin/ch/ChakraRtInterface.h @@ -86,6 +86,7 @@ struct JsAPIHooks typedef JsErrorCode(WINAPI *JsrtCreateExternalArrayBuffer)(void *data, unsigned int byteLength, JsFinalizeCallback finalizeCallback, void *callbackState, JsValueRef *result); typedef JsErrorCode(WINAPI *JsrtCreatePropertyId)(const char *name, size_t length, JsPropertyIdRef *propertyId); + typedef JsErrorCode(WINAPI *JsrtGetProxyProperties)(JsValueRef object, bool* isProxy, JsValueRef* target, JsValueRef* handler); typedef JsErrorCode(WINAPI *JsrtTTDCreateRecordRuntimePtr)(JsRuntimeAttributes attributes, bool enableDebugging, size_t snapInterval, size_t snapHistoryLength, TTDOpenResourceStreamCallback openResourceStream, JsTTDWriteBytesToStreamCallback writeBytesToStream, JsTTDFlushAndCloseStreamCallback flushAndCloseStream, JsThreadServiceCallback threadService, JsRuntimeHandle *runtime); typedef JsErrorCode(WINAPI *JsrtTTDCreateReplayRuntimePtr)(JsRuntimeAttributes attributes, const char* infoUri, size_t infoUriCount, bool enableDebugging, TTDOpenResourceStreamCallback openResourceStream, JsTTDReadBytesFromStreamCallback readBytesFromStream, JsTTDFlushAndCloseStreamCallback flushAndCloseStream, JsThreadServiceCallback threadService, JsRuntimeHandle *runtime); @@ -183,6 +184,7 @@ struct JsAPIHooks JsrtCopyString pfJsrtCopyString; JsrtCreatePropertyId pfJsrtCreatePropertyId; JsrtCreateExternalArrayBuffer pfJsrtCreateExternalArrayBuffer; + JsrtGetProxyProperties pfJsrtGetProxyProperties; JsrtTTDCreateRecordRuntimePtr pfJsrtTTDCreateRecordRuntime; JsrtTTDCreateReplayRuntimePtr pfJsrtTTDCreateReplayRuntime; @@ -412,6 +414,7 @@ class ChakraRTInterface static JsErrorCode WINAPI JsCreateStringUtf16(const uint16_t *content, size_t length, JsValueRef *value) { return HOOK_JS_API(CreateStringUtf16(content, length, value)); } static JsErrorCode WINAPI JsCreatePropertyId(const char *name, size_t length, JsPropertyIdRef *propertyId) { return HOOK_JS_API(CreatePropertyId(name, length, propertyId)); } static JsErrorCode WINAPI JsCreateExternalArrayBuffer(void *data, unsigned int byteLength, JsFinalizeCallback finalizeCallback, void *callbackState, JsValueRef *result) { return HOOK_JS_API(CreateExternalArrayBuffer(data, byteLength, finalizeCallback, callbackState, result)); } + static JsErrorCode WINAPI JsGetProxyProperties(JsValueRef object, bool* isProxy, JsValueRef* target, JsValueRef* handler) { return HOOK_JS_API(GetProxyProperties(object, isProxy, target, handler)); } }; class AutoRestoreContext diff --git a/bin/ch/WScriptJsrt.cpp b/bin/ch/WScriptJsrt.cpp index 9b56f621611..83641502fac 100644 --- a/bin/ch/WScriptJsrt.cpp +++ b/bin/ch/WScriptJsrt.cpp @@ -859,6 +859,7 @@ bool WScriptJsrt::Initialize() IfFalseGo(WScriptJsrt::InstallObjectsOnObject(wscript, "LoadTextFile", LoadTextFileCallback)); IfFalseGo(WScriptJsrt::InstallObjectsOnObject(wscript, "Flag", FlagCallback)); IfFalseGo(WScriptJsrt::InstallObjectsOnObject(wscript, "RegisterModuleSource", RegisterModuleSourceCallback)); + IfFalseGo(WScriptJsrt::InstallObjectsOnObject(wscript, "GetProxyProperties", GetProxyPropertiesCallback)); // ToDo Remove IfFalseGo(WScriptJsrt::InstallObjectsOnObject(wscript, "Edit", EmptyCallback)); @@ -1371,6 +1372,57 @@ JsValueRef __stdcall WScriptJsrt::SleepCallback(JsValueRef callee, bool isConstr return returnValue; } +JsValueRef __stdcall WScriptJsrt::GetProxyPropertiesCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState) +{ + HRESULT hr = E_FAIL; + JsValueRef returnValue = JS_INVALID_REFERENCE; + JsValueRef undefined = JS_INVALID_REFERENCE; + JsErrorCode errorCode = JsNoError; + + IfJsrtErrorSetGo(ChakraRTInterface::JsGetUndefinedValue(&undefined)); + + returnValue = undefined; + + if (argumentCount > 1) + { + bool isProxy = false; + JsValueRef target; + JsValueRef handler; + IfJsrtErrorSetGo(ChakraRTInterface::JsGetProxyProperties(arguments[1], &isProxy, &target, &handler)); + + if (isProxy) + { + JsPropertyIdRef targetProperty; + JsPropertyIdRef handlerProperty; + JsPropertyIdRef revokedProperty; + + IfJsrtErrorSetGo(CreatePropertyIdFromString("target", &targetProperty)); + IfJsrtErrorSetGo(CreatePropertyIdFromString("handler", &handlerProperty)); + IfJsrtErrorSetGo(CreatePropertyIdFromString("revoked", &revokedProperty)); + IfJsrtErrorSetGo(ChakraRTInterface::JsCreateObject(&returnValue)); + + JsValueRef revoked = JS_INVALID_REFERENCE; + + if (target == JS_INVALID_REFERENCE) + { + IfJsrtErrorSetGo(ChakraRTInterface::JsGetTrueValue(&revoked)); + target = undefined; + handler = undefined; + } + else + { + IfJsrtErrorSetGo(ChakraRTInterface::JsGetFalseValue(&revoked)); + } + + IfJsrtErrorSetGo(ChakraRTInterface::JsSetProperty(returnValue, handlerProperty, handler, true)); + IfJsrtErrorSetGo(ChakraRTInterface::JsSetProperty(returnValue, targetProperty, target, true)); + IfJsrtErrorSetGo(ChakraRTInterface::JsSetProperty(returnValue, revokedProperty, revoked, true)); + } + } +Error: + return returnValue; +} + bool WScriptJsrt::PrintException(LPCSTR fileName, JsErrorCode jsErrorCode) { LPCWSTR errorTypeString = ConvertErrorCodeToMessage(jsErrorCode); diff --git a/bin/ch/WScriptJsrt.h b/bin/ch/WScriptJsrt.h index 1946d9dd923..9ab3fc38c25 100644 --- a/bin/ch/WScriptJsrt.h +++ b/bin/ch/WScriptJsrt.h @@ -129,6 +129,7 @@ class WScriptJsrt static JsValueRef CALLBACK GetReportCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); static JsValueRef CALLBACK LeavingCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); static JsValueRef CALLBACK SleepCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); + static JsValueRef CALLBACK GetProxyPropertiesCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); static JsErrorCode FetchImportedModuleHelper(JsModuleRecord referencingModule, JsValueRef specifier, __out JsModuleRecord* dependentModuleRecord, LPCSTR refdir = nullptr); diff --git a/lib/Jsrt/ChakraCore.h b/lib/Jsrt/ChakraCore.h index e789eae9f0f..fd265063297 100644 --- a/lib/Jsrt/ChakraCore.h +++ b/lib/Jsrt/ChakraCore.h @@ -1035,5 +1035,33 @@ CHAKRA_API JsSetHostPromiseRejectionTracker( _In_ JsHostPromiseRejectionTrackerCallback promiseRejectionTrackerCallback, _In_opt_ void *callbackState); + +/// +/// Determines if a provided object is a JavscriptProxy Object and +/// provides references to a Proxy's target and handler. +/// +/// +/// Requires an active script context. +/// If object is not a Proxy object the target and handler parameters are not touched. +/// If nullptr is supplied for target or handler the function returns after +/// setting the isProxy value. +/// If the object is a revoked Proxy target and handler are set to JS_INVALID_REFERENCE. +/// If it is a Proxy object that has not been revoked target and handler are set to the +/// the object's target and handler. +/// +/// The object that may be a Proxy. +/// Pointer to a Boolean - is the object a proxy? +/// Pointer to a JsValueRef - the object's target. +/// Pointer to a JsValueRef - the object's handler. +/// +/// The code JsNoError if the operation succeeded, a failure code otherwise. +/// +CHAKRA_API + JsGetProxyProperties( + _In_ JsValueRef object, + _Out_ bool* isProxy, + _Out_opt_ JsValueRef* target, + _Out_opt_ JsValueRef* handler); + #endif // _CHAKRACOREBUILD #endif // _CHAKRACORE_H_ diff --git a/lib/Jsrt/Jsrt.cpp b/lib/Jsrt/Jsrt.cpp index 00d9150c38e..b94ea75f17d 100644 --- a/lib/Jsrt/Jsrt.cpp +++ b/lib/Jsrt/Jsrt.cpp @@ -5329,4 +5329,45 @@ CHAKRA_API JsSetHostPromiseRejectionTracker(_In_ JsHostPromiseRejectionTrackerCa /*allowInObjectBeforeCollectCallback*/true); } +CHAKRA_API JsGetProxyProperties (_In_ JsValueRef object, _Out_ bool* isProxy, _Out_opt_ JsValueRef* target, _Out_opt_ JsValueRef* handler) +{ + return ContextAPINoScriptWrapper_NoRecord([&](Js::ScriptContext * scriptContext) -> JsErrorCode { + VALIDATE_INCOMING_REFERENCE(object, scriptContext); + PARAM_NOT_NULL(isProxy); + + if (target != nullptr) + { + *target = JS_INVALID_REFERENCE; + } + + if (handler != nullptr) + { + *handler = JS_INVALID_REFERENCE; + } + + *isProxy = Js::JavascriptProxy::Is(object); + + if (!*isProxy) + { + return JsNoError; + } + + Js::JavascriptProxy* proxy = Js::JavascriptProxy::UnsafeFromVar(object); + bool revoked = proxy->IsRevoked(); + + if (target != nullptr && !revoked) + { + *target = static_cast(proxy->GetTarget()); + } + + if (handler != nullptr && !revoked) + { + *handler = static_cast(proxy->GetHandler()); + } + + return JsNoError; + }, + /*allowInObjectBeforeCollectCallback*/true); +} + #endif // _CHAKRACOREBUILD diff --git a/lib/Runtime/Library/JavascriptProxy.cpp b/lib/Runtime/Library/JavascriptProxy.cpp index 9e5965b473b..83e0528b8c6 100644 --- a/lib/Runtime/Library/JavascriptProxy.cpp +++ b/lib/Runtime/Library/JavascriptProxy.cpp @@ -16,6 +16,11 @@ namespace Js return JavascriptOperators::GetTypeId(obj) == TypeIds_Proxy; } + bool JavascriptProxy::IsRevoked() const + { + return (target == nullptr); + } + RecyclableObject* JavascriptProxy::GetTarget() { if (target == nullptr) diff --git a/lib/Runtime/Library/JavascriptProxy.h b/lib/Runtime/Library/JavascriptProxy.h index 75e4ecfc84f..6472abf5862 100644 --- a/lib/Runtime/Library/JavascriptProxy.h +++ b/lib/Runtime/Library/JavascriptProxy.h @@ -154,6 +154,7 @@ namespace Js virtual RecyclableObject* ToObject(ScriptContext * requestContext) override; virtual Var GetTypeOfString(ScriptContext* requestContext) override; + bool IsRevoked() const; BOOL SetPropertyTrap(Var receiver, SetPropertyTrapKind setPropertyTrapKind, PropertyId propertyId, Var newValue, ScriptContext* requestContext, BOOL skipPrototypeCheck = FALSE); BOOL SetPropertyTrap(Var receiver, SetPropertyTrapKind setPropertyTrapKind, Js::JavascriptString * propertyString, Var newValue, ScriptContext* requestContext); diff --git a/test/es6/ProxyPropertiesAPI.js b/test/es6/ProxyPropertiesAPI.js new file mode 100644 index 00000000000..13b4377869a --- /dev/null +++ b/test/es6/ProxyPropertiesAPI.js @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); + +let tests = [ + { + name: "GetProxyProperties: no argugments", + body: function () { + let properties = WScript.GetProxyProperties(); + assert.isUndefined(properties, "ProxyProperties of nothing should be undefined."); + } + }, + { + name: "GetProxyProperties: non-proxy arguments", + body: function () { + let properties = WScript.GetProxyProperties(undefined); + assert.isUndefined(properties, "ProxyProperties of undefined should be undefined."); + properties = WScript.GetProxyProperties(1); + assert.isUndefined(properties, "ProxyProperties of number should be undefined."); + properties = WScript.GetProxyProperties({}); + assert.isUndefined(properties, "ProxyProperties of non-proxy object should be undefined."); + } + }, + { + name: "GetProxyProperties: revocable Proxy", + body: function () { + let revocable = Proxy.revocable({someProperty: true, otherProperty: false}, {otherProperty: true, newProperty: 5}); + let proxy = revocable.proxy; + let properties = WScript.GetProxyProperties(proxy); + + let names = Object.getOwnPropertyNames(properties); + assert.areEqual(names.length, 3, "proxy properties names should have length 3."); + assert.isTrue(names.includes("target")); + assert.isTrue(names.includes("handler")); + assert.isTrue(names.includes("revoked")); + assert.isFalse(properties.revoked, "Revoked bool should be false."); + + names = Object.getOwnPropertyNames(properties.target); + assert.areEqual(names.length, 2, "proxy properties target names should have length 2."); + assert.areEqual(properties.target.someProperty, true); + assert.areEqual(properties.target.otherProperty, false); + + names = Object.getOwnPropertyNames(properties.handler); + assert.areEqual(names.length, 2, "proxy properties handler names should have length 2."); + assert.areEqual(properties.handler.newProperty, 5); + assert.areEqual(properties.handler.otherProperty, true); + + revocable.revoke(); + properties = WScript.GetProxyProperties(proxy); + + names = Object.getOwnPropertyNames(properties); + assert.areEqual(names.length, 3, "proxy properties names for revokes proxy should have length 3."); + assert.isTrue(names.includes("target")); + assert.isTrue(names.includes("handler")); + assert.isTrue(properties.revoked, "Revoked bool should be true."); + + assert.isUndefined(properties.target, "Target of revoked proxy should be undefined."); + assert.isUndefined(properties.handler, "Handler of revoked proxy should be undefined."); + } + }, + { + name: "GetProxyProperties: normal Proxy", + body: function () { + let proxy = new Proxy({someProperty: true, otherProperty: false}, {otherProperty: true, newProperty: 5}); + let properties = WScript.GetProxyProperties(proxy); + + let names = Object.getOwnPropertyNames(properties); + assert.areEqual(names.length, 3, "proxy properties names should have length 3"); + assert.isTrue(names.includes("target")); + assert.isTrue(names.includes("handler")); + assert.isTrue(names.includes("revoked")); + assert.isFalse(properties.revoked, "Revoked bool should be false."); + + names = Object.getOwnPropertyNames(properties.target); + assert.areEqual(names.length, 2, "proxy properties target names should have length 2"); + assert.areEqual(properties.target.someProperty, true); + assert.areEqual(properties.target.otherProperty, false); + + names = Object.getOwnPropertyNames(properties.handler); + assert.areEqual(names.length, 2, "proxy properties handler names should have length 2"); + assert.areEqual(properties.handler.newProperty, 5); + assert.areEqual(properties.handler.otherProperty, true); + } + } +]; + +testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" }); \ No newline at end of file diff --git a/test/es6/rlexe.xml b/test/es6/rlexe.xml index 406c76c648a..c3f4b44e820 100644 --- a/test/es6/rlexe.xml +++ b/test/es6/rlexe.xml @@ -715,6 +715,13 @@ -args summary -endargs + + + ProxyPropertiesAPI.js + -args summary -endargs + exclude_jshost + + proxybugs.js