From 7bf59d1286b2f35383c05859afdddba0a6092570 Mon Sep 17 00:00:00 2001 From: Oguz Bastemur Date: Wed, 15 Nov 2017 03:45:36 +0100 Subject: [PATCH] jsrt: JsCreateExternalObjectWithPrototype --- lib/Jsrt/ChakraCore.h | 22 +++ lib/Jsrt/Jsrt.cpp | 36 ++++- lib/Jsrt/JsrtCommonExports.inc | 8 + lib/Jsrt/JsrtExternalObject.cpp | 11 +- lib/Jsrt/JsrtExternalObject.h | 2 +- .../test-static-external/Platform.js | 52 ++++++ .../test-static-external/sample.cpp | 153 ++++++++++++++++++ 7 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 test/native-tests/test-static-external/Platform.js create mode 100644 test/native-tests/test-static-external/sample.cpp diff --git a/lib/Jsrt/ChakraCore.h b/lib/Jsrt/ChakraCore.h index 2cdf6b07b22..2b122972748 100644 --- a/lib/Jsrt/ChakraCore.h +++ b/lib/Jsrt/ChakraCore.h @@ -792,6 +792,28 @@ CHAKRA_API _In_ JsValueRef object2, _Out_ bool *result); +/// +/// Creates a new object (with prototype) that stores some external data. +/// +/// +/// Requires an active script context. +/// +/// External data that the object will represent. May be null. +/// +/// A callback for when the object is finalized. May be null. +/// +/// Prototype object or nullptr. +/// The new object. +/// +/// The code JsNoError if the operation succeeded, a failure code otherwise. +/// +CHAKRA_API + JsCreateExternalObjectWithPrototype( + _In_opt_ void *data, + _In_opt_ JsFinalizeCallback finalizeCallback, + _In_ JsValueRef prototype, + _Out_ JsValueRef *object); + /// /// Gets an object's property. /// diff --git a/lib/Jsrt/Jsrt.cpp b/lib/Jsrt/Jsrt.cpp index 413c2b83ca1..941e234f988 100644 --- a/lib/Jsrt/Jsrt.cpp +++ b/lib/Jsrt/Jsrt.cpp @@ -208,7 +208,7 @@ void CALLBACK CreateExternalObject_TTDCallback(Js::ScriptContext* ctx, Js::Var* { TTDAssert(object != nullptr, "This should always be a valid location"); - *object = JsrtExternalObject::Create(nullptr, nullptr, ctx); + *object = JsrtExternalObject::Create(nullptr, nullptr, nullptr, ctx); } void CALLBACK TTDDummyPromiseContinuationCallback(JsValueRef task, void *callbackState) @@ -1285,7 +1285,7 @@ CHAKRA_API JsCreateExternalObject(_In_opt_ void *data, _In_opt_ JsFinalizeCallba PARAM_NOT_NULL(object); - *object = JsrtExternalObject::Create(data, finalizeCallback, scriptContext); + *object = JsrtExternalObject::Create(data, finalizeCallback, nullptr, scriptContext); PERFORM_JSRT_TTD_RECORD_ACTION_RESULT(scriptContext, object); @@ -1293,6 +1293,38 @@ CHAKRA_API JsCreateExternalObject(_In_opt_ void *data, _In_opt_ JsFinalizeCallba }); } +#ifndef NTBUILD +CHAKRA_API JsCreateExternalObjectWithPrototype(_In_opt_ void *data, + _In_opt_ JsFinalizeCallback finalizeCallback, + _In_ JsValueRef prototype, + _Out_ JsValueRef *object) +{ + return ContextAPINoScriptWrapper([&](Js::ScriptContext *scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode { + PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTAllocateExternalObject); + + PARAM_NOT_NULL(object); + + Js::RecyclableObject * prototypeObject = nullptr; + if (prototype != nullptr) + { + VALIDATE_INCOMING_OBJECT(prototype, scriptContext); + prototypeObject = Js::RecyclableObject::FromVar(prototype); + } + + *object = JsrtExternalObject::Create(data, finalizeCallback, prototypeObject, scriptContext); + + PERFORM_JSRT_TTD_RECORD_ACTION_RESULT(scriptContext, object); + + if (prototypeObject != nullptr) + { + PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTSetPrototype, *object, prototypeObject); + } + + return JsNoError; + }); +} +#endif + CHAKRA_API JsConvertValueToObject(_In_ JsValueRef value, _Out_ JsValueRef *result) { return ContextAPIWrapper([&] (Js::ScriptContext *scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode { diff --git a/lib/Jsrt/JsrtCommonExports.inc b/lib/Jsrt/JsrtCommonExports.inc index c10e758521d..610d026aa7c 100644 --- a/lib/Jsrt/JsrtCommonExports.inc +++ b/lib/Jsrt/JsrtCommonExports.inc @@ -122,4 +122,12 @@ JsHasOwnProperty JsCopyStringOneByte JsGetDataViewInfo + JsCreateExternalObjectWithPrototype + JsObjectGetProperty + JsObjectHasProperty + JsObjectSetProperty + JsObjectDeleteProperty + JsObjectHasOwnProperty + JsObjectGetOwnPropertyDescriptor + JsObjectDefineProperty #endif diff --git a/lib/Jsrt/JsrtExternalObject.cpp b/lib/Jsrt/JsrtExternalObject.cpp index b005cd08dab..fe36826070c 100644 --- a/lib/Jsrt/JsrtExternalObject.cpp +++ b/lib/Jsrt/JsrtExternalObject.cpp @@ -28,7 +28,7 @@ JsrtExternalObject::JsrtExternalObject(JsrtExternalType * type, void *data) : } /* static */ -JsrtExternalObject* JsrtExternalObject::Create(void *data, JsFinalizeCallback finalizeCallback, Js::ScriptContext *scriptContext) +JsrtExternalObject* JsrtExternalObject::Create(void *data, JsFinalizeCallback finalizeCallback, Js::RecyclableObject * prototype, Js::ScriptContext *scriptContext) { Js::DynamicType * dynamicType = scriptContext->GetLibrary()->GetCachedJsrtExternalType(reinterpret_cast(finalizeCallback)); @@ -41,7 +41,14 @@ JsrtExternalObject* JsrtExternalObject::Create(void *data, JsFinalizeCallback fi Assert(dynamicType->IsJsrtExternal()); Assert(dynamicType->GetIsShared()); - return RecyclerNewFinalized(scriptContext->GetRecycler(), JsrtExternalObject, static_cast(dynamicType), data); + JsrtExternalObject * externalObject = RecyclerNewFinalized(scriptContext->GetRecycler(), JsrtExternalObject, static_cast(dynamicType), data); + + if (prototype != nullptr) + { + externalObject->SetPrototype(prototype); + } + + return externalObject; } bool JsrtExternalObject::Is(Js::Var value) diff --git a/lib/Jsrt/JsrtExternalObject.h b/lib/Jsrt/JsrtExternalObject.h index 7da472d43ea..0f648bbb6be 100644 --- a/lib/Jsrt/JsrtExternalObject.h +++ b/lib/Jsrt/JsrtExternalObject.h @@ -50,7 +50,7 @@ class JsrtExternalObject : public Js::DynamicObject static bool Is(Js::Var value); static JsrtExternalObject * FromVar(Js::Var value); static JsrtExternalObject * UnsafeFromVar(Js::Var value); - static JsrtExternalObject * Create(void *data, JsFinalizeCallback finalizeCallback, Js::ScriptContext *scriptContext); + static JsrtExternalObject * Create(void *data, JsFinalizeCallback finalizeCallback, Js::RecyclableObject * prototype, Js::ScriptContext *scriptContext); JsrtExternalType * GetExternalType() const { return (JsrtExternalType *)this->GetType(); } diff --git a/test/native-tests/test-static-external/Platform.js b/test/native-tests/test-static-external/Platform.js new file mode 100644 index 00000000000..48c25f2e6cb --- /dev/null +++ b/test/native-tests/test-static-external/Platform.js @@ -0,0 +1,52 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +var isWindows = !WScript.Platform || WScript.Platform.OS == 'win32'; +var path_sep = isWindows ? '\\' : '/'; +var isStaticBuild = WScript.Platform && WScript.Platform.LINK_TYPE == 'static'; + +if (!isStaticBuild) { + // test will be ignored + print("# IGNORE_THIS_TEST"); +} else { + var platform = WScript.Platform.OS; + var binaryPath = WScript.Platform.BINARY_PATH; + // discard `ch` from path + binaryPath = binaryPath.substr(0, binaryPath.lastIndexOf(path_sep)); + var makefile = +"IDIR=" + binaryPath + "/../../lib/Jsrt \n\ +\n\ +LIBRARY_PATH=" + binaryPath + "/lib\n\ +PLATFORM=" + platform + "\n\ +LDIR=$(LIBRARY_PATH)/libChakraCoreStatic.a \n\ +\n\ +ifeq (darwin, ${PLATFORM})\n\ +\tICU4C_LIBRARY_PATH ?= /usr/local/opt/icu4c\n\ +\tCFLAGS=-lstdc++ -std=c++11 -I$(IDIR)\n\ +\tFORCE_STARTS=-Wl,-force_load,\n\ +\tFORCE_ENDS=\n\ +\tLIBS=-framework CoreFoundation -framework Security -lm -ldl -Wno-c++11-compat-deprecated-writable-strings \ + -Wno-deprecated-declarations -Wno-unknown-warning-option -o sample.o\n\ +\tLDIR+=$(ICU4C_LIBRARY_PATH)/lib/libicudata.a \ + $(ICU4C_LIBRARY_PATH)/lib/libicuuc.a \ + $(ICU4C_LIBRARY_PATH)/lib/libicui18n.a\n\ +else\n\ +\tCFLAGS=-lstdc++ -std=c++0x -I$(IDIR)\n\ +\tFORCE_STARTS=-Wl,--whole-archive\n\ +\tFORCE_ENDS=-Wl,--no-whole-archive\n\ +\tLIBS=-pthread -lm -ldl -licuuc -Wno-c++11-compat-deprecated-writable-strings \ + -Wno-deprecated-declarations -Wno-unknown-warning-option -o sample.o\n\ +endif\n\ +\n\ +testmake:\n\ +\t$(CC) sample.cpp $(CFLAGS) $(FORCE_STARTS) $(LDIR) $(FORCE_ENDS) $(LIBS)\n\ +\n\ +.PHONY: clean\n\ +\n\ +clean:\n\ +\trm sample.o\n"; + + print(makefile) +} diff --git a/test/native-tests/test-static-external/sample.cpp b/test/native-tests/test-static-external/sample.cpp new file mode 100644 index 00000000000..22ff9932d4c --- /dev/null +++ b/test/native-tests/test-static-external/sample.cpp @@ -0,0 +1,153 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +#include "ChakraCore.h" +#include +#include +#include +#include + +#define FAIL_CHECK(cmd) \ + do \ + { \ + JsErrorCode errCode = (JsErrorCode) cmd; \ + if (errCode != JsNoError) \ + { \ + printf("Error %d at '%s'\n", \ + errCode, #cmd); \ + return 1; \ + } \ + } while(0) + +using namespace std; + +unsigned int AddMethodProperty(JsValueRef target, const char* str, + JsNativeFunction func) +{ + JsPropertyIdRef Id; + FAIL_CHECK(JsCreatePropertyId(str, strlen(str), &Id)); + + JsValueRef nameVar; + FAIL_CHECK(JsCreateString(str, strlen(str), &nameVar)); + + JsValueRef functionVar; + FAIL_CHECK(JsCreateNamedFunction(nameVar, func, nullptr, &functionVar)); + + FAIL_CHECK(JsSetProperty(target, Id, functionVar, true)); + + return JsNoError; +} + +JsValueRef CHECK(JsValueRef callee, bool isConstructCall, + JsValueRef * arguments, unsigned short argumentCount, void * callbackState) +{ + void * data; + if (JsGetExternalData(arguments[1], &data) == JsNoError && + ((size_t*)data)[122] - ((size_t*)data)[0] == 41976) + { + fprintf(stdout, "SU"); // CCESS will be printed during finalizer call + } + else + { + fprintf(stderr, "External data corrupt?"); + } + + return nullptr; +} + +JsValueRef createWithProto(JsValueRef callee, bool isConstructCall, + JsValueRef * arguments, unsigned short argumentCount, void * callbackState) +{ + size_t *data = new size_t[123]; + data[0] = 12345; + data[122] = 54321; + + JsValueRef obj; + + JsErrorCode code = JsCreateExternalObjectWithPrototype(data, [](void* buffer) + { + size_t * mem = (size_t*)buffer; + if(!(mem[122] - mem[0] == 41976)) abort(); + delete mem; + + // test tool needs to see `SUCCESS` to determine if script was + // successful. So far the test printed `SU`, + // this should print the last part `CCESS` + fprintf(stdout, "CCESS\n"); + }, arguments[1], &obj); + + if (code != JsNoError) + { + printf("FAILED to create external object %d \n", code); + } + + return obj; +} + +int main() +{ + JsRuntimeHandle runtime; + JsContextRef context; + JsValueRef result; + unsigned currentSourceContext = 0; + + const char* script = "\ + (function(){\ + var proto = { sub: 3 }; \ + var ext = CreateWithProto(proto); \ + if (ext.sub === 3) CHECK(ext); \ + return \'\'; \ + })();\ + "; + + // Create a runtime. + JsCreateRuntime(JsRuntimeAttributeNone, nullptr, &runtime); + + // Create an execution context. + JsCreateContext(runtime, &context); + + // Now set the current execution context. + JsSetCurrentContext(context); + + JsValueRef fname; + FAIL_CHECK(JsCreateString("sample", strlen("sample"), &fname)); + + JsValueRef scriptSource; + FAIL_CHECK(JsCreateExternalArrayBuffer((void*)script, + (unsigned)strlen(script), + nullptr, nullptr, &scriptSource)); + + JsValueRef global; + FAIL_CHECK(JsGetGlobalObject(&global)); + + FAIL_CHECK(AddMethodProperty(global, "CreateWithProto", &createWithProto)); + FAIL_CHECK(AddMethodProperty(global, "CHECK", &CHECK)); + + // Run the script. + FAIL_CHECK(JsRun(scriptSource, currentSourceContext++, fname, + JsParseScriptAttributeNone, &result)); + + // Convert your script result to String in JavaScript; + // redundant if your script returns a String + JsValueRef resultJSString; + FAIL_CHECK(JsConvertValueToString(result, &resultJSString)); + + // Project script result back to C++. + char *resultSTR = nullptr; + size_t stringLength; + FAIL_CHECK(JsCopyString(resultJSString, nullptr, 0, &stringLength)); + resultSTR = (char*)malloc(stringLength + 1); + FAIL_CHECK(JsCopyString(resultJSString, resultSTR, stringLength, nullptr)); + resultSTR[stringLength] = 0; + + fprintf(stdout, "%s", resultSTR); // prints an empty string `` + free(resultSTR); + + // Dispose runtime + JsSetCurrentContext(JS_INVALID_REFERENCE); + JsDisposeRuntime(runtime); + + return 0; +}